//========================================================================
//
// PSOutputDev.cc
//
// Copyright 1996-2003 Glyph & Cog, LLC
//
//========================================================================

#include <aconf.h>

#ifdef USE_GCC_PRAGMAS
#pragma implementation
#endif

#include <stdio.h>
#include <stddef.h>
#include <stdarg.h>
#include <signal.h>
#include <math.h>
#include "GString.h"
#include "GList.h"
#include "config.h"
#include "GlobalParams.h"
#include "Object.h"
#include "Error.h"
#include "Function.h"
#include "Gfx.h"
#include "GfxState.h"
#include "GfxFont.h"
#include "UnicodeMap.h"
#include "FoFiType1C.h"
#include "FoFiTrueType.h"
#include "Catalog.h"
#include "Page.h"
#include "Stream.h"
#include "Annot.h"
#include "XRef.h"
#include "PreScanOutputDev.h"
#if HAVE_SPLASH
#  include "Splash.h"
#  include "SplashBitmap.h"
#  include "SplashOutputDev.h"
#endif
#include "PSOutputDev.h"

#ifdef MACOS
// needed for setting type/creator of MacOS files
#include "ICSupport.h"
#endif

// the MSVC math.h doesn't define this
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

//------------------------------------------------------------------------

// Resolution at which pages with transparency will be rasterized.
#define splashDPI 300

//------------------------------------------------------------------------
// PostScript prolog and setup
//------------------------------------------------------------------------

// The '~' escapes mark prolog code that is emitted only in certain
// levels:
//
//   ~[123][sn]
//      ^   ^----- s=psLevel*Sep, n=psLevel*
//      +----- 1=psLevel1*, 2=psLevel2*, 3=psLevel3*

static char *prolog[] = {
    "/xpdf 75 dict def xpdf begin",
    "% PDF special state",
    "/pdfDictSize 15 def",
    "~1sn",
    "/pdfStates 64 array def",
    "  0 1 63 {",
    "    pdfStates exch pdfDictSize dict",
    "    dup /pdfStateIdx 3 index put",
    "    put",
    "  } for",
    "~123sn",
    "/pdfSetup {",
    "  3 1 roll 2 array astore",
    "  /setpagedevice where {",
    "    pop 3 dict begin",
    "      /PageSize exch def",
    "      /ImagingBBox null def",
    "      /Policies 1 dict dup begin /PageSize 3 def end def",
    "      { /Duplex true def } if",
    "    currentdict end setpagedevice",
    "  } {",
    "    pop pop",
    "  } ifelse",
    "} def",
    "~1sn",
    "/pdfOpNames [",
    "  /pdfFill /pdfStroke /pdfLastFill /pdfLastStroke",
    "  /pdfTextMat /pdfFontSize /pdfCharSpacing /pdfTextRender",
    "  /pdfTextRise /pdfWordSpacing /pdfHorizScaling /pdfTextClipPath",
    "] def",
    "~123sn",
    "/pdfStartPage {",
    "~1sn",
    "  pdfStates 0 get begin",
    "~23sn",
    "  pdfDictSize dict begin",
    "~23n",
    "  /pdfFillCS [] def",
    "  /pdfFillXform {} def",
    "  /pdfStrokeCS [] def",
    "  /pdfStrokeXform {} def",
    "~1n",
    "  /pdfFill 0 def",
    "  /pdfStroke 0 def",
    "~1s",
    "  /pdfFill [0 0 0 1] def",
    "  /pdfStroke [0 0 0 1] def",
    "~23sn",
    "  /pdfFill [0] def",
    "  /pdfStroke [0] def",
    "  /pdfFillOP false def",
    "  /pdfStrokeOP false def",
    "~123sn",
    "  /pdfLastFill false def",
    "  /pdfLastStroke false def",
    "  /pdfTextMat [1 0 0 1 0 0] def",
    "  /pdfFontSize 0 def",
    "  /pdfCharSpacing 0 def",
    "  /pdfTextRender 0 def",
    "  /pdfTextRise 0 def",
    "  /pdfWordSpacing 0 def",
    "  /pdfHorizScaling 1 def",
    "  /pdfTextClipPath [] def",
    "} def",
    "/pdfEndPage { end } def",
    "~23s",
    "% separation convention operators",
    "/findcmykcustomcolor where {",
    "  pop",
    "}{",
    "  /findcmykcustomcolor { 5 array astore } def",
    "} ifelse",
    "/setcustomcolor where {",
    "  pop",
    "}{",
    "  /setcustomcolor {",
    "    exch",
    "    [ exch /Separation exch dup 4 get exch /DeviceCMYK exch",
    "      0 4 getinterval cvx",
    "      [ exch /dup load exch { mul exch dup } /forall load",
    "        /pop load dup ] cvx",
    "    ] setcolorspace setcolor",
    "  } def",
    "} ifelse",
    "/customcolorimage where {",
    "  pop",
    "}{",
    "  /customcolorimage {",
    "    gsave",
    "    [ exch /Separation exch dup 4 get exch /DeviceCMYK exch",
    "      0 4 getinterval",
    "      [ exch /dup load exch { mul exch dup } /forall load",
    "        /pop load dup ] cvx",
    "    ] setcolorspace",
    "    10 dict begin",
    "      /ImageType 1 def",
    "      /DataSource exch def",
    "      /ImageMatrix exch def",
    "      /BitsPerComponent exch def",
    "      /Height exch def",
    "      /Width exch def",
    "      /Decode [1 0] def",
    "    currentdict end",
    "    image",
    "    grestore",
    "  } def",
    "} ifelse",
    "~123sn",
    "% PDF color state",
    "~1n",
    "/g { dup /pdfFill exch def setgray",
    "     /pdfLastFill true def /pdfLastStroke false def } def",
    "/G { dup /pdfStroke exch def setgray",
    "     /pdfLastStroke true def /pdfLastFill false def } def",
    "/fCol {",
    "  pdfLastFill not {",
    "    pdfFill setgray",
    "    /pdfLastFill true def /pdfLastStroke false def",
    "  } if",
    "} def",
    "/sCol {",
    "  pdfLastStroke not {",
    "    pdfStroke setgray",
    "    /pdfLastStroke true def /pdfLastFill false def",
    "  } if",
    "} def",
    "~1s",
    "/k { 4 copy 4 array astore /pdfFill exch def setcmykcolor",
    "     /pdfLastFill true def /pdfLastStroke false def } def",
    "/K { 4 copy 4 array astore /pdfStroke exch def setcmykcolor",
    "     /pdfLastStroke true def /pdfLastFill false def } def",
    "/fCol {",
    "  pdfLastFill not {",
    "    pdfFill aload pop setcmykcolor",
    "    /pdfLastFill true def /pdfLastStroke false def",
    "  } if",
    "} def",
    "/sCol {",
    "  pdfLastStroke not {",
    "    pdfStroke aload pop setcmykcolor",
    "    /pdfLastStroke true def /pdfLastFill false def",
    "  } if",
    "} def",
    "~23n",
    "/cs { /pdfFillXform exch def dup /pdfFillCS exch def",
    "      setcolorspace } def",
    "/CS { /pdfStrokeXform exch def dup /pdfStrokeCS exch def",
    "      setcolorspace } def",
    "/sc { pdfLastFill not { pdfFillCS setcolorspace } if",
    "      dup /pdfFill exch def aload pop pdfFillXform setcolor",
    "     /pdfLastFill true def /pdfLastStroke false def } def",
    "/SC { pdfLastStroke not { pdfStrokeCS setcolorspace } if",
    "      dup /pdfStroke exch def aload pop pdfStrokeXform setcolor",
    "     /pdfLastStroke true def /pdfLastFill false def } def",
    "/op { /pdfFillOP exch def",
    "      pdfLastFill { pdfFillOP setoverprint } if } def",
    "/OP { /pdfStrokeOP exch def",
    "      pdfLastStroke { pdfStrokeOP setoverprint } if } def",
    "/fCol {",
    "  pdfLastFill not {",
    "    pdfFillCS setcolorspace",
    "    pdfFill aload pop pdfFillXform setcolor",
    "    pdfFillOP setoverprint",
    "    /pdfLastFill true def /pdfLastStroke false def",
    "  } if",
    "} def",
    "/sCol {",
    "  pdfLastStroke not {",
    "    pdfStrokeCS setcolorspace",
    "    pdfStroke aload pop pdfStrokeXform setcolor",
    "    pdfStrokeOP setoverprint",
    "    /pdfLastStroke true def /pdfLastFill false def",
    "  } if",
    "} def",
    "~23s",
    "/k { 4 copy 4 array astore /pdfFill exch def setcmykcolor",
    "     /pdfLastFill true def /pdfLastStroke false def } def",
    "/K { 4 copy 4 array astore /pdfStroke exch def setcmykcolor",
    "     /pdfLastStroke true def /pdfLastFill false def } def",
    "/ck { 6 copy 6 array astore /pdfFill exch def",
    "      findcmykcustomcolor exch setcustomcolor",
    "      /pdfLastFill true def /pdfLastStroke false def } def",
    "/CK { 6 copy 6 array astore /pdfStroke exch def",
    "      findcmykcustomcolor exch setcustomcolor",
    "      /pdfLastStroke true def /pdfLastFill false def } def",
    "/op { /pdfFillOP exch def",
    "      pdfLastFill { pdfFillOP setoverprint } if } def",
    "/OP { /pdfStrokeOP exch def",
    "      pdfLastStroke { pdfStrokeOP setoverprint } if } def",
    "/fCol {",
    "  pdfLastFill not {",
    "    pdfFill aload length 4 eq {",
    "      setcmykcolor",
    "    }{",
    "      findcmykcustomcolor exch setcustomcolor",
    "    } ifelse",
    "    pdfFillOP setoverprint",
    "    /pdfLastFill true def /pdfLastStroke false def",
    "  } if",
    "} def",
    "/sCol {",
    "  pdfLastStroke not {",
    "    pdfStroke aload length 4 eq {",
    "      setcmykcolor",
    "    }{",
    "      findcmykcustomcolor exch setcustomcolor",
    "    } ifelse",
    "    pdfStrokeOP setoverprint",
    "    /pdfLastStroke true def /pdfLastFill false def",
    "  } if",
    "} def",
    "~123sn",
    "% build a font",
    "/pdfMakeFont {",
    "  4 3 roll findfont",
    "  4 2 roll matrix scale makefont",
    "  dup length dict begin",
    "    { 1 index /FID ne { def } { pop pop } ifelse } forall",
    "    /Encoding exch def",
    "    currentdict",
    "  end",
    "  definefont pop",
    "} def",
    "/pdfMakeFont16 {",
    "  exch findfont",
    "  dup length dict begin",
    "    { 1 index /FID ne { def } { pop pop } ifelse } forall",
    "    /WMode exch def",
    "    currentdict",
    "  end",
    "  definefont pop",
    "} def",
    "~3sn",
    "/pdfMakeFont16L3 {",
    "  1 index /CIDFont resourcestatus {",
    "    pop pop 1 index /CIDFont findresource /CIDFontType known",
    "  } {",
    "    false",
    "  } ifelse",
    "  {",
    "    0 eq { /Identity-H } { /Identity-V } ifelse",
    "    exch 1 array astore composefont pop",
    "  } {",
    "    pdfMakeFont16",
    "  } ifelse",
    "} def",
    "~123sn",
    "% graphics state operators",
    "~1sn",
    "/q {",
    "  gsave",
    "  pdfOpNames length 1 sub -1 0 { pdfOpNames exch get load } for",
    "  pdfStates pdfStateIdx 1 add get begin",
    "  pdfOpNames { exch def } forall",
    "} def",
    "/Q { end grestore } def",
    "~23sn",
    "/q { gsave pdfDictSize dict begin } def",
    "/Q {",
    "  end grestore",
    "  /pdfLastFill where {",
    "    pop",
    "    pdfLastFill {",
    "      pdfFillOP setoverprint",
    "    } {",
    "      pdfStrokeOP setoverprint",
    "    } ifelse",
    "  } if",
    "} def",
    "~123sn",
    "/cm { concat } def",
    "/d { setdash } def",
    "/i { setflat } def",
    "/j { setlinejoin } def",
    "/J { setlinecap } def",
    "/M { setmiterlimit } def",
    "/w { setlinewidth } def",
    "% path segment operators",
    "/m { moveto } def",
    "/l { lineto } def",
    "/c { curveto } def",
    "/re { 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto",
    "      neg 0 rlineto closepath } def",
    "/h { closepath } def",
    "% path painting operators",
    "/S { sCol stroke } def",
    "/Sf { fCol stroke } def",
    "/f { fCol fill } def",
    "/f* { fCol eofill } def",
    "% clipping operators",
    "/W { clip newpath } def",
    "/W* { eoclip newpath } def",
    "/Ws { strokepath clip newpath } def",
    "% text state operators",
    "/Tc { /pdfCharSpacing exch def } def",
    "/Tf { dup /pdfFontSize exch def",
    "      dup pdfHorizScaling mul exch matrix scale",
    "      pdfTextMat matrix concatmatrix dup 4 0 put dup 5 0 put",
    "      exch findfont exch makefont setfont } def",
    "/Tr { /pdfTextRender exch def } def",
    "/Ts { /pdfTextRise exch def } def",
    "/Tw { /pdfWordSpacing exch def } def",
    "/Tz { /pdfHorizScaling exch def } def",
    "% text positioning operators",
    "/Td { pdfTextMat transform moveto } def",
    "/Tm { /pdfTextMat exch def } def",
    "% text string operators",
    "/cshow where {",
    "  pop",
    "  /cshow2 {",
    "    dup {",
    "      pop pop",
    "      1 string dup 0 3 index put 3 index exec",
    "    } exch cshow",
    "    pop pop",
    "  } def",
    "}{",
    "  /cshow2 {",
    "    currentfont /FontType get 0 eq {",
    "      0 2 2 index length 1 sub {",
    "        2 copy get exch 1 add 2 index exch get",
    "        2 copy exch 256 mul add",
    "        2 string dup 0 6 5 roll put dup 1 5 4 roll put",
    "        3 index exec",
    "      } for",
    "    } {",
    "      dup {",
    "        1 string dup 0 3 index put 3 index exec",
    "      } forall",
    "    } ifelse",
    "    pop pop",
    "  } def",
    "} ifelse",
    "/awcp {", // awidthcharpath
    "  exch {",
    "    false charpath",
    "    5 index 5 index rmoveto",
    "    6 index eq { 7 index 7 index rmoveto } if",
    "  } exch cshow2",
    "  6 {pop} repeat",
    "} def",
    "/Tj {",
    "  fCol",  // because stringwidth has to draw Type 3 chars
    "  1 index stringwidth pdfTextMat idtransform pop",
    "  sub 1 index length dup 0 ne { div } { pop pop 0 } ifelse",
    "  pdfWordSpacing pdfHorizScaling mul 0 pdfTextMat dtransform 32",
    "  4 3 roll pdfCharSpacing pdfHorizScaling mul add 0",
    "  pdfTextMat dtransform",
    "  6 5 roll Tj1",
    "} def",
    "/Tj16 {",
    "  fCol",  // because stringwidth has to draw Type 3 chars
    "  2 index stringwidth pdfTextMat idtransform pop",
    "  sub exch div",
    "  pdfWordSpacing pdfHorizScaling mul 0 pdfTextMat dtransform 32",
    "  4 3 roll pdfCharSpacing pdfHorizScaling mul add 0",
    "  pdfTextMat dtransform",
    "  6 5 roll Tj1",
    "} def",
    "/Tj16V {",
    "  fCol",  // because stringwidth has to draw Type 3 chars
    "  2 index stringwidth pdfTextMat idtransform exch pop",
    "  sub exch div",
    "  0 pdfWordSpacing pdfTextMat dtransform 32",
    "  4 3 roll pdfCharSpacing add 0 exch",
    "  pdfTextMat dtransform",
    "  6 5 roll Tj1",
    "} def",
    "/Tj1 {",
    "  0 pdfTextRise pdfTextMat dtransform rmoveto",
    "  currentpoint 8 2 roll",
    "  pdfTextRender 1 and 0 eq {",
    "    6 copy awidthshow",
    "  } if",
    "  pdfTextRender 3 and dup 1 eq exch 2 eq or {",
    "    7 index 7 index moveto",
    "    6 copy",
    "    currentfont /FontType get 3 eq { fCol } { sCol } ifelse",
    "    false awcp currentpoint stroke moveto",
    "  } if",
    "  pdfTextRender 4 and 0 ne {",
    "    8 6 roll moveto",
    "    false awcp",
    "    /pdfTextClipPath [ pdfTextClipPath aload pop",
    "      {/moveto cvx}",
    "      {/lineto cvx}",
    "      {/curveto cvx}",
    "      {/closepath cvx}",
    "    pathforall ] def",
    "    currentpoint newpath moveto",
    "  } {",
    "    8 {pop} repeat",
    "  } ifelse",
    "  0 pdfTextRise neg pdfTextMat dtransform rmoveto",
    "} def",
    "/TJm { pdfFontSize 0.001 mul mul neg 0",
    "       pdfTextMat dtransform rmoveto } def",
    "/TJmV { pdfFontSize 0.001 mul mul neg 0 exch",
    "        pdfTextMat dtransform rmoveto } def",
    "/Tclip { pdfTextClipPath cvx exec clip newpath",
    "         /pdfTextClipPath [] def } def",
    "~1ns",
    "% Level 1 image operators",
    "~1n",
    "/pdfIm1 {",
    "  /pdfImBuf1 4 index string def",
    "  { currentfile pdfImBuf1 readhexstring pop } image",
    "} def",
    "~1s",
    "/pdfIm1Sep {",
    "  /pdfImBuf1 4 index string def",
    "  /pdfImBuf2 4 index string def",
    "  /pdfImBuf3 4 index string def",
    "  /pdfImBuf4 4 index string def",
    "  { currentfile pdfImBuf1 readhexstring pop }",
    "  { currentfile pdfImBuf2 readhexstring pop }",
    "  { currentfile pdfImBuf3 readhexstring pop }",
    "  { currentfile pdfImBuf4 readhexstring pop }",
    "  true 4 colorimage",
    "} def",
    "~1ns",
    "/pdfImM1 {",
    "  fCol /pdfImBuf1 4 index 7 add 8 idiv string def",
    "  { currentfile pdfImBuf1 readhexstring pop } imagemask",
    "} def",
    "/pdfImM1a {",
    "  { 2 copy get exch 1 add exch } imagemask",
    "  pop pop",
    "} def",
    "~23sn",
    "% Level 2 image operators",
    "/pdfImBuf 100 string def",
    "/pdfIm {",
    "  image",
    "  { currentfile pdfImBuf readline",
    "    not { pop exit } if",
    "    (%-EOD-) eq { exit } if } loop",
    "} def",
    "~23s",
    "/pdfImSep {",
    "  findcmykcustomcolor exch",
    "  dup /Width get /pdfImBuf1 exch string def",
    "  dup /Decode get aload pop 1 index sub /pdfImDecodeRange exch def",
    "  /pdfImDecodeLow exch def",
    "  begin Width Height BitsPerComponent ImageMatrix DataSource end",
    "  /pdfImData exch def",
    "  { pdfImData pdfImBuf1 readstring pop",
    "    0 1 2 index length 1 sub {",
    "      1 index exch 2 copy get",
    "      pdfImDecodeRange mul 255 div pdfImDecodeLow add round cvi",
    "      255 exch sub put",
    "    } for }",
    "  6 5 roll customcolorimage",
    "  { currentfile pdfImBuf readline",
    "    not { pop exit } if",
    "    (%-EOD-) eq { exit } if } loop",
    "} def",
    "~23sn",
    "/pdfImM {",
    "  fCol imagemask",
    "  { currentfile pdfImBuf readline",
    "    not { pop exit } if",
    "    (%-EOD-) eq { exit } if } loop",
    "} def",
    "/pr { 2 index 2 index 3 2 roll putinterval 4 add } def",
    "/pdfImClip {",
    "  gsave",
    "  0 2 4 index length 1 sub {",
    "    dup 4 index exch 2 copy",
    "    get 5 index div put",
    "    1 add 3 index exch 2 copy",
    "    get 3 index div put",
    "  } for",
    "  pop pop rectclip",
    "} def",
    "/pdfImClipEnd { grestore } def",
    "~23sn",
    "% shading operators",
    "/colordelta {",
    "  false 0 1 3 index length 1 sub {",
    "    dup 4 index exch get 3 index 3 2 roll get sub abs 0.004 gt {",
    "      pop true",
    "    } if",
    "  } for",
    "  exch pop exch pop",
    "} def",
    "/funcCol { func n array astore } def",
    "/funcSH {",
    "  dup 0 eq {",
    "    true",
    "  } {",
    "    dup 6 eq {",
    "      false",
    "    } {",
    "      4 index 4 index funcCol dup",
    "      6 index 4 index funcCol dup",
    "      3 1 roll colordelta 3 1 roll",
    "      5 index 5 index funcCol dup",
    "      3 1 roll colordelta 3 1 roll",
    "      6 index 8 index funcCol dup",
    "      3 1 roll colordelta 3 1 roll",
    "      colordelta or or or",
    "    } ifelse",
    "  } ifelse",
    "  {",
    "    1 add",
    "    4 index 3 index add 0.5 mul exch 4 index 3 index add 0.5 mul exch",
    "    6 index 6 index 4 index 4 index 4 index funcSH",
    "    2 index 6 index 6 index 4 index 4 index funcSH",
    "    6 index 2 index 4 index 6 index 4 index funcSH",
    "    5 3 roll 3 2 roll funcSH pop pop",
    "  } {",
    "    pop 3 index 2 index add 0.5 mul 3 index  2 index add 0.5 mul",
    "~23n",
    "    funcCol sc",
    "~23s",
    "    funcCol aload pop k",
    "~23sn",
    "    dup 4 index exch mat transform m",
    "    3 index 3 index mat transform l",
    "    1 index 3 index mat transform l",
    "    mat transform l pop pop h f*",
    "  } ifelse",
    "} def",
    "/axialCol {",
    "  dup 0 lt {",
    "    pop t0",
    "  } {",
    "    dup 1 gt {",
    "      pop t1",
    "    } {",
    "      dt mul t0 add",
    "    } ifelse",
    "  } ifelse",
    "  func n array astore",
    "} def",
    "/axialSH {",
    "  dup 0 eq {",
    "    true",
    "  } {",
    "    dup 8 eq {",
    "      false",
    "    } {",
    "      2 index axialCol 2 index axialCol colordelta",
    "    } ifelse",
    "  } ifelse",
    "  {",
    "    1 add 3 1 roll 2 copy add 0.5 mul",
    "    dup 4 3 roll exch 4 index axialSH",
    "    exch 3 2 roll axialSH",
    "  } {",
    "    pop 2 copy add 0.5 mul",
    "~23n",
    "    axialCol sc",
    "~23s",
    "    axialCol aload pop k",
    "~23sn",
    "    exch dup dx mul x0 add exch dy mul y0 add",
    "    3 2 roll dup dx mul x0 add exch dy mul y0 add",
    "    dx abs dy abs ge {",
    "      2 copy yMin sub dy mul dx div add yMin m",
    "      yMax sub dy mul dx div add yMax l",
    "      2 copy yMax sub dy mul dx div add yMax l",
    "      yMin sub dy mul dx div add yMin l",
    "      h f*",
    "    } {",
    "      exch 2 copy xMin sub dx mul dy div add xMin exch m",
    "      xMax sub dx mul dy div add xMax exch l",
    "      exch 2 copy xMax sub dx mul dy div add xMax exch l",
    "      xMin sub dx mul dy div add xMin exch l",
    "      h f*",
    "    } ifelse",
    "  } ifelse",
    "} def",
    "/radialCol {",
    "  dup t0 lt {",
    "    pop t0",
    "  } {",
    "    dup t1 gt {",
    "      pop t1",
    "    } if",
    "  } ifelse",
    "  func n array astore",
    "} def",
    "/radialSH {",
    "  dup 0 eq {",
    "    true",
    "  } {",
    "    dup 8 eq {",
    "      false",
    "    } {",
    "      2 index dt mul t0 add radialCol",
    "      2 index dt mul t0 add radialCol colordelta",
    "    } ifelse",
    "  } ifelse",
    "  {",
    "    1 add 3 1 roll 2 copy add 0.5 mul",
    "    dup 4 3 roll exch 4 index radialSH",
    "    exch 3 2 roll radialSH",
    "  } {",
    "    pop 2 copy add 0.5 mul dt mul t0 add",
    "~23n",
    "    radialCol sc",
    "~23s",
    "    radialCol aload pop k",
    "~23sn",
    "    encl {",
    "      exch dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add",
    "      0 360 arc h",
    "      dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add",
    "      360 0 arcn h f",
    "    } {",
    "      2 copy",
    "      dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add",
    "      a1 a2 arcn",
    "      dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add",
    "      a2 a1 arcn h",
    "      dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add",
    "      a1 a2 arc",
    "      dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add",
    "      a2 a1 arc h f",
    "    } ifelse",
    "  } ifelse",
    "} def",
    "~123sn",
    "end",
    NULL
};

static char *cmapProlog[] = {
    "/CIDInit /ProcSet findresource begin",
    "10 dict begin",
    "  begincmap",
    "  /CMapType 1 def",
    "  /CMapName /Identity-H def",
    "  /CIDSystemInfo 3 dict dup begin",
    "    /Registry (Adobe) def",
    "    /Ordering (Identity) def",
    "    /Supplement 0 def",
    "  end def",
    "  1 begincodespacerange",
    "    <0000> <ffff>",
    "  endcodespacerange",
    "  0 usefont",
    "  1 begincidrange",
    "    <0000> <ffff> 0",
    "  endcidrange",
    "  endcmap",
    "  currentdict CMapName exch /CMap defineresource pop",
    "end",
    "10 dict begin",
    "  begincmap",
    "  /CMapType 1 def",
    "  /CMapName /Identity-V def",
    "  /CIDSystemInfo 3 dict dup begin",
    "    /Registry (Adobe) def",
    "    /Ordering (Identity) def",
    "    /Supplement 0 def",
    "  end def",
    "  /WMode 1 def",
    "  1 begincodespacerange",
    "    <0000> <ffff>",
    "  endcodespacerange",
    "  0 usefont",
    "  1 begincidrange",
    "    <0000> <ffff> 0",
    "  endcidrange",
    "  endcmap",
    "  currentdict CMapName exch /CMap defineresource pop",
    "end",
    "end",
    NULL
};

//------------------------------------------------------------------------
// Fonts
//------------------------------------------------------------------------

struct PSSubstFont {
    char *psName;			// PostScript name
    double mWidth;		// width of 'm' character
};

static char *psFonts[] = {
    "Courier",
    "Courier-Bold",
    "Courier-Oblique",
    "Courier-BoldOblique",
    "Helvetica",
    "Helvetica-Bold",
    "Helvetica-Oblique",
    "Helvetica-BoldOblique",
    "Symbol",
    "Times-Roman",
    "Times-Bold",
    "Times-Italic",
    "Times-BoldItalic",
    "ZapfDingbats",
    NULL
};

static PSSubstFont psSubstFonts[] = {
    {"Helvetica",             0.833},
    {"Helvetica-Oblique",     0.833},
    {"Helvetica-Bold",        0.889},
    {"Helvetica-BoldOblique", 0.889},
    {"Times-Roman",           0.788},
    {"Times-Italic",          0.722},
    {"Times-Bold",            0.833},
    {"Times-BoldItalic",      0.778},
    {"Courier",               0.600},
    {"Courier-Oblique",       0.600},
    {"Courier-Bold",          0.600},
    {"Courier-BoldOblique",   0.600}
};

// Info for 8-bit fonts
struct PSFont8Info {
    Ref fontID;
    Gushort *codeToGID;		// code-to-GID mapping for TrueType fonts
};

// Encoding info for substitute 16-bit font
struct PSFont16Enc {
    Ref fontID;
    GString *enc;
};

//------------------------------------------------------------------------
// process colors
//------------------------------------------------------------------------

#define psProcessCyan     1
#define psProcessMagenta  2
#define psProcessYellow   4
#define psProcessBlack    8
#define psProcessCMYK    15

//------------------------------------------------------------------------
// PSOutCustomColor
//------------------------------------------------------------------------

class PSOutCustomColor {
public:
    
    PSOutCustomColor(double cA, double mA,
                     double yA, double kA, GString *nameA);
    ~PSOutCustomColor();
    
    double c, m, y, k;
    GString *name;
    PSOutCustomColor *next;
};

PSOutCustomColor::PSOutCustomColor(double cA, double mA,
                                   double yA, double kA, GString *nameA) {
    c = cA;
    m = mA;
    y = yA;
    k = kA;
    name = nameA;
    next = NULL;
}

PSOutCustomColor::~PSOutCustomColor() {
    delete name;
}

//------------------------------------------------------------------------

struct PSOutImgClipRect {
    int x0, x1, y0, y1;
};

//------------------------------------------------------------------------
// DeviceNRecoder
//------------------------------------------------------------------------

class DeviceNRecoder: public FilterStream {
public:
    
    DeviceNRecoder(Stream *strA, int widthA, int heightA,
                   GfxImageColorMap *colorMapA);
    virtual ~DeviceNRecoder();
    virtual StreamKind getKind() { return strWeird; }
    virtual void reset();
    virtual int getChar()
    { return (bufIdx >= bufSize && !fillBuf()) ? EOF : buf[bufIdx++]; }
    virtual int lookChar()
    { return (bufIdx >= bufSize && !fillBuf()) ? EOF : buf[bufIdx]; }
    virtual GString *getPSFilter(int psLevel, char *indent) { return NULL; }
    virtual GBool isBinary(GBool last = gTrue) { return gTrue; }
    virtual GBool isEncoder() { return gTrue; }
    
private:
    
    GBool fillBuf();
    
    int width, height;
    GfxImageColorMap *colorMap;
    Function *func;
    ImageStream *imgStr;
    int buf[gfxColorMaxComps];
    int pixelIdx;
    int bufIdx;
    int bufSize;
};

DeviceNRecoder::DeviceNRecoder(Stream *strA, int widthA, int heightA,
                               GfxImageColorMap *colorMapA):
FilterStream(strA) {
    width = widthA;
    height = heightA;
    colorMap = colorMapA;
    imgStr = NULL;
    pixelIdx = 0;
    bufIdx = gfxColorMaxComps;
    bufSize = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->
    getAlt()->getNComps();
    func = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->
    getTintTransformFunc();
}

DeviceNRecoder::~DeviceNRecoder() {
    if (imgStr) {
        delete imgStr;
    }
}

void DeviceNRecoder::reset() {
    imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(),
                             colorMap->getBits());
    imgStr->reset();
}

GBool DeviceNRecoder::fillBuf() {
    Guchar pixBuf[gfxColorMaxComps];
    GfxColor color;
    double x[gfxColorMaxComps], y[gfxColorMaxComps];
    int i;
    
    if (pixelIdx >= width * height) {
        return gFalse;
    }
    imgStr->getPixel(pixBuf);
    colorMap->getColor(pixBuf, &color);
    for (i = 0;
         i < ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->getNComps();
         ++i) {
        x[i] = colToDbl(color.c[i]);
    }
    func->transform(x, y);
    for (i = 0; i < bufSize; ++i) {
        buf[i] = (int)(y[i] * 255 + 0.5);
    }
    bufIdx = 0;
    ++pixelIdx;
    return gTrue;
}

//------------------------------------------------------------------------
// PSOutputDev
//------------------------------------------------------------------------

extern "C" {
    typedef void (*SignalFunc)(int);
}

static void outputToFile(void *stream, char *data, int len) {
    fwrite(data, 1, len, (FILE *)stream);
}

PSOutputDev::PSOutputDev(char *fileName, XRef *xrefA, Catalog *catalog,
                         int firstPage, int lastPage, PSOutMode modeA,
                         int imgLLXA, int imgLLYA, int imgURXA, int imgURYA,
                         GBool manualCtrlA) {
    FILE *f;
    PSFileType fileTypeA;
    
    underlayCbk = NULL;
    underlayCbkData = NULL;
    overlayCbk = NULL;
    overlayCbkData = NULL;
    
    fontIDs = NULL;
    fontFileIDs = NULL;
    fontFileNames = NULL;
    font8Info = NULL;
    font16Enc = NULL;
    imgIDs = NULL;
    formIDs = NULL;
    xobjStack = NULL;
    embFontList = NULL;
    customColors = NULL;
    haveTextClip = gFalse;
    t3String = NULL;
    
    // open file or pipe
    if (!strcmp(fileName, "-")) {
        fileTypeA = psStdout;
        f = stdout;
    } else if (fileName[0] == '|') {
        fileTypeA = psPipe;
#ifdef HAVE_POPEN
#ifndef WIN32
        signal(SIGPIPE, (SignalFunc)SIG_IGN);
#endif
        if (!(f = popen(fileName + 1, "w"))) {
            error(-1, "Couldn't run print command '%s'", fileName);
            ok = gFalse;
            return;
        }
#else
        error(-1, "Print commands are not supported ('%s')", fileName);
        ok = gFalse;
        return;
#endif
    } else {
        fileTypeA = psFile;
        if (!(f = fopen(fileName, "w"))) {
            error(-1, "Couldn't open PostScript file '%s'", fileName);
            ok = gFalse;
            return;
        }
    }
    
    init(outputToFile, f, fileTypeA,
         xrefA, catalog, firstPage, lastPage, modeA,
         imgLLXA, imgLLYA, imgURXA, imgURYA, manualCtrlA);
}

PSOutputDev::PSOutputDev(PSOutputFunc outputFuncA, void *outputStreamA,
                         XRef *xrefA, Catalog *catalog,
                         int firstPage, int lastPage, PSOutMode modeA,
                         int imgLLXA, int imgLLYA, int imgURXA, int imgURYA,
                         GBool manualCtrlA) {
    underlayCbk = NULL;
    underlayCbkData = NULL;
    overlayCbk = NULL;
    overlayCbkData = NULL;
    
    fontIDs = NULL;
    fontFileIDs = NULL;
    fontFileNames = NULL;
    font8Info = NULL;
    font16Enc = NULL;
    imgIDs = NULL;
    formIDs = NULL;
    xobjStack = NULL;
    embFontList = NULL;
    customColors = NULL;
    haveTextClip = gFalse;
    t3String = NULL;
    
    init(outputFuncA, outputStreamA, psGeneric,
         xrefA, catalog, firstPage, lastPage, modeA,
         imgLLXA, imgLLYA, imgURXA, imgURYA, manualCtrlA);
}

void PSOutputDev::init(PSOutputFunc outputFuncA, void *outputStreamA,
                       PSFileType fileTypeA, XRef *xrefA, Catalog *catalog,
                       int firstPage, int lastPage, PSOutMode modeA,
                       int imgLLXA, int imgLLYA, int imgURXA, int imgURYA,
                       GBool manualCtrlA) {
    Page *page;
    PDFRectangle *box;
    
    // initialize
    ok = gTrue;
    outputFunc = outputFuncA;
    outputStream = outputStreamA;
    fileType = fileTypeA;
    xref = xrefA;
    level = globalParams->getPSLevel();
    mode = modeA;
    paperWidth = globalParams->getPSPaperWidth();
    paperHeight = globalParams->getPSPaperHeight();
    imgLLX = imgLLXA;
    imgLLY = imgLLYA;
    imgURX = imgURXA;
    imgURY = imgURYA;
    if (imgLLX == 0 && imgURX == 0 && imgLLY == 0 && imgURY == 0) {
        globalParams->getPSImageableArea(&imgLLX, &imgLLY, &imgURX, &imgURY);
    }
    if (paperWidth < 0 || paperHeight < 0) {
        // this check is needed in case the document has zero pages
        if (firstPage > 0 && firstPage <= catalog->getNumPages()) {
            page = catalog->getPage(firstPage);
            paperWidth = (int)ceil(page->getMediaWidth());
            paperHeight = (int)ceil(page->getMediaHeight());
        } else {
            paperWidth = 1;
            paperHeight = 1;
        }
        imgLLX = imgLLY = 0;
        imgURX = paperWidth;
        imgURY = paperHeight;
    }
    preload = globalParams->getPSPreload();
    manualCtrl = manualCtrlA;
    if (mode == psModeForm) {
        lastPage = firstPage;
    }
    processColors = 0;
    inType3Char = gFalse;
    
#if OPI_SUPPORT
    // initialize OPI nesting levels
    opi13Nest = 0;
    opi20Nest = 0;
#endif
    
    tx0 = ty0 = -1;
    xScale0 = yScale0 = 0;
    rotate0 = -1;
    clipLLX0 = clipLLY0 = 0;
    clipURX0 = clipURY0 = -1;
    
    // initialize fontIDs, fontFileIDs, and fontFileNames lists
    fontIDSize = 64;
    fontIDLen = 0;
    fontIDs = (Ref *)gmallocn(fontIDSize, sizeof(Ref));
    fontFileIDSize = 64;
    fontFileIDLen = 0;
    fontFileIDs = (Ref *)gmallocn(fontFileIDSize, sizeof(Ref));
    fontFileNameSize = 64;
    fontFileNameLen = 0;
    fontFileNames = (GString **)gmallocn(fontFileNameSize, sizeof(GString *));
    nextTrueTypeNum = 0;
    font8InfoLen = 0;
    font8InfoSize = 0;
    font16EncLen = 0;
    font16EncSize = 0;
    imgIDLen = 0;
    imgIDSize = 0;
    formIDLen = 0;
    formIDSize = 0;
    
    xobjStack = new GList();
    numSaves = 0;
    numTilingPatterns = 0;
    nextFunc = 0;
    
    // initialize embedded font resource comment list
    embFontList = new GString();
    
    if (!manualCtrl) {
        // this check is needed in case the document has zero pages
        if (firstPage > 0 && firstPage <= catalog->getNumPages()) {
            writeHeader(firstPage, lastPage,
                        catalog->getPage(firstPage)->getMediaBox(),
                        catalog->getPage(firstPage)->getCropBox(),
                        catalog->getPage(firstPage)->getRotate());
        } else {
            box = new PDFRectangle(0, 0, 1, 1);
            writeHeader(firstPage, lastPage, box, box, 0);
            delete box;
        }
        if (mode != psModeForm) {
            writePS("%%BeginProlog\n");
        }
        writeXpdfProcset();
        if (mode != psModeForm) {
            writePS("%%EndProlog\n");
            writePS("%%BeginSetup\n");
        }
        writeDocSetup(catalog, firstPage, lastPage);
        if (mode != psModeForm) {
            writePS("%%EndSetup\n");
        }
    }
    
    // initialize sequential page number
    seqPage = 1;
}

PSOutputDev::~PSOutputDev() {
    PSOutCustomColor *cc;
    int i;
    
    if (ok) {
        if (!manualCtrl) {
            writePS("%%Trailer\n");
            writeTrailer();
            if (mode != psModeForm) {
                writePS("%%EOF\n");
            }
        }
        if (fileType == psFile) {
#ifdef MACOS
            ICS_MapRefNumAndAssign((short)((FILE *)outputStream)->handle);
#endif
            fclose((FILE *)outputStream);
        }
#ifdef HAVE_POPEN
        else if (fileType == psPipe) {
            pclose((FILE *)outputStream);
#ifndef WIN32
            signal(SIGPIPE, (SignalFunc)SIG_DFL);
#endif
        }
#endif
    }
    if (embFontList) {
        delete embFontList;
    }
    if (fontIDs) {
        gfree(fontIDs);
    }
    if (fontFileIDs) {
        gfree(fontFileIDs);
    }
    if (fontFileNames) {
        for (i = 0; i < fontFileNameLen; ++i) {
            delete fontFileNames[i];
        }
        gfree(fontFileNames);
    }
    if (font8Info) {
        for (i = 0; i < font8InfoLen; ++i) {
            gfree(font8Info[i].codeToGID);
        }
        gfree(font8Info);
    }
    if (font16Enc) {
        for (i = 0; i < font16EncLen; ++i) {
            delete font16Enc[i].enc;
        }
        gfree(font16Enc);
    }
    gfree(imgIDs);
    gfree(formIDs);
    if (xobjStack) {
        delete xobjStack;
    }
    while (customColors) {
        cc = customColors;
        customColors = cc->next;
        delete cc;
    }
}

void PSOutputDev::writeHeader(int firstPage, int lastPage,
                              PDFRectangle *mediaBox, PDFRectangle *cropBox,
                              int pageRotate) {
    Object info, obj1;
    double x1, y1, x2, y2;
    
    switch (mode) {
        case psModePS:
            writePS("%!PS-Adobe-3.0\n");
            break;
        case psModeEPS:
            writePS("%!PS-Adobe-3.0 EPSF-3.0\n");
            break;
        case psModeForm:
            writePS("%!PS-Adobe-3.0 Resource-Form\n");
            break;
    }
    
    writePSFmt("% Produced by xpdf/pdftops {0:s}\n", xpdfVersion);
    xref->getDocInfo(&info);
    if (info.isDict() && info.dictLookup("Creator", &obj1)->isString()) {
        writePS("%%Creator: ");
        writePSTextLine(obj1.getString());
    }
    obj1.free();
    if (info.isDict() && info.dictLookup("Title", &obj1)->isString()) {
        writePS("%%Title: ");
        writePSTextLine(obj1.getString());
    }
    obj1.free();
    info.free();
    writePSFmt("%%LanguageLevel: {0:d}\n",
               (level == psLevel1 || level == psLevel1Sep) ? 1 :
               (level == psLevel2 || level == psLevel2Sep) ? 2 : 3);
    if (level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep) {
        writePS("%%DocumentProcessColors: (atend)\n");
        writePS("%%DocumentCustomColors: (atend)\n");
    }
    writePS("%%DocumentSuppliedResources: (atend)\n");
    
    switch (mode) {
        case psModePS:
            writePSFmt("%%DocumentMedia: plain {0:d} {1:d} 0 () ()\n",
                       paperWidth, paperHeight);
            writePSFmt("%%BoundingBox: 0 0 {0:d} {1:d}\n", paperWidth, paperHeight);
            writePSFmt("%%Pages: {0:d}\n", lastPage - firstPage + 1);
            writePS("%%EndComments\n");
            writePS("%%BeginDefaults\n");
            writePS("%%PageMedia: plain\n");
            writePS("%%EndDefaults\n");
            break;
        case psModeEPS:
            epsX1 = cropBox->x1;
            epsY1 = cropBox->y1;
            epsX2 = cropBox->x2;
            epsY2 = cropBox->y2;
            if (pageRotate == 0 || pageRotate == 180) {
                x1 = epsX1;
                y1 = epsY1;
                x2 = epsX2;
                y2 = epsY2;
            } else { // pageRotate == 90 || pageRotate == 270
                x1 = 0;
                y1 = 0;
                x2 = epsY2 - epsY1;
                y2 = epsX2 - epsX1;
            }
            writePSFmt("%%BoundingBox: {0:d} {1:d} {2:d} {3:d}\n",
                       (int)floor(x1), (int)floor(y1), (int)ceil(x2), (int)ceil(y2));
            if (floor(x1) != ceil(x1) || floor(y1) != ceil(y1) ||
                floor(x2) != ceil(x2) || floor(y2) != ceil(y2)) {
                writePSFmt("%%HiResBoundingBox: {0:.4g} {1:.4g} {2:.4g} {3:.4g}\n",
                           x1, y1, x2, y2);
            }
            writePS("%%EndComments\n");
            break;
        case psModeForm:
            writePS("%%EndComments\n");
            writePS("32 dict dup begin\n");
            writePSFmt("/BBox [{0:d} {1:d} {2:d} {3:d}] def\n",
                       (int)floor(mediaBox->x1), (int)floor(mediaBox->y1),
                       (int)ceil(mediaBox->x2), (int)ceil(mediaBox->y2));
            writePS("/FormType 1 def\n");
            writePS("/Matrix [1 0 0 1 0 0] def\n");
            break;
    }
}

void PSOutputDev::writeXpdfProcset() {
    GBool lev1, lev2, lev3, sep, nonSep;
    char **p;
    char *q;
    
    writePSFmt("%%BeginResource: procset xpdf {0:s} 0\n", xpdfVersion);
    writePSFmt("%%Copyright: {0:s}\n", xpdfCopyright);
    lev1 = lev2 = lev3 = sep = nonSep = gTrue;
    for (p = prolog; *p; ++p) {
        if ((*p)[0] == '~') {
            lev1 = lev2 = lev3 = sep = nonSep = gFalse;
            for (q = *p + 1; *q; ++q) {
                switch (*q) {
                    case '1': lev1 = gTrue; break;
                    case '2': lev2 = gTrue; break;
                    case '3': lev3 = gTrue; break;
                    case 's': sep = gTrue; break;
                    case 'n': nonSep = gTrue; break;
                }
            }
        } else if ((level == psLevel1 && lev1 && nonSep) ||
                   (level == psLevel1Sep && lev1 && sep) ||
                   (level == psLevel2 && lev2 && nonSep) ||
                   (level == psLevel2Sep && lev2 && sep) ||
                   (level == psLevel3 && lev3 && nonSep) ||
                   (level == psLevel3Sep && lev3 && sep)) {
            writePSFmt("{0:s}\n", *p);
        }
    }
    writePS("%%EndResource\n");
    
    if (level >= psLevel3) {
        for (p = cmapProlog; *p; ++p) {
            writePSFmt("{0:s}\n", *p);
        }
    }
}

void PSOutputDev::writeDocSetup(Catalog *catalog,
                                int firstPage, int lastPage) {
    Page *page;
    Dict *resDict;
    Annots *annots;
    Object obj1, obj2;
    int pg, i;
    
    if (mode == psModeForm) {
        // swap the form and xpdf dicts
        writePS("xpdf end begin dup begin\n");
    } else {
        writePS("xpdf begin\n");
    }
    for (pg = firstPage; pg <= lastPage; ++pg) {
        page = catalog->getPage(pg);
        if ((resDict = page->getResourceDict())) {
            setupResources(resDict);
        }
        annots = new Annots(xref, catalog, page->getAnnots(&obj1));
        obj1.free();
        for (i = 0; i < annots->getNumAnnots(); ++i) {
            if (annots->getAnnot(i)->getAppearance(&obj1)->isStream()) {
                obj1.streamGetDict()->lookup("Resources", &obj2);
                if (obj2.isDict()) {
                    setupResources(obj2.getDict());
                }
                obj2.free();
            }
            obj1.free();
        }
        delete annots;
    }
    if (mode != psModeForm) {
        if (mode != psModeEPS && !manualCtrl) {
            writePSFmt("{0:d} {1:d} {2:s} pdfSetup\n",
                       paperWidth, paperHeight,
                       globalParams->getPSDuplex() ? "true" : "false");
        }
#if OPI_SUPPORT
        if (globalParams->getPSOPI()) {
            writePS("/opiMatrix matrix currentmatrix def\n");
        }
#endif
    }
}

void PSOutputDev::writePageTrailer() {
    if (mode != psModeForm) {
        writePS("pdfEndPage\n");
    }
}

void PSOutputDev::writeTrailer() {
    PSOutCustomColor *cc;
    
    if (mode == psModeForm) {
        writePS("/Foo exch /Form defineresource pop\n");
    } else {
        writePS("end\n");
        writePS("%%DocumentSuppliedResources:\n");
        writePS(embFontList->getCString());
        if (level == psLevel1Sep || level == psLevel2Sep ||
            level == psLevel3Sep) {
            writePS("%%DocumentProcessColors:");
            if (processColors & psProcessCyan) {
                writePS(" Cyan");
            }
            if (processColors & psProcessMagenta) {
                writePS(" Magenta");
            }
            if (processColors & psProcessYellow) {
                writePS(" Yellow");
            }
            if (processColors & psProcessBlack) {
                writePS(" Black");
            }
            writePS("\n");
            writePS("%%DocumentCustomColors:");
            for (cc = customColors; cc; cc = cc->next) {
                writePSFmt(" ({0:s})", cc->name->getCString());
            }
            writePS("\n");
            writePS("%%CMYKCustomColor:\n");
            for (cc = customColors; cc; cc = cc->next) {
                writePSFmt("%%+ {0:.4g} {1:.4g} {2:.4g} {3:.4g} ({4:t})\n",
                           cc->c, cc->m, cc->y, cc->k, cc->name);
            }
        }
    }
}

void PSOutputDev::setupResources(Dict *resDict) {
    Object xObjDict, xObjRef, xObj, patDict, patRef, pat, resObj;
    Ref ref0, ref1;
    GBool skip;
    int i, j;
    
    setupFonts(resDict);
    setupImages(resDict);
    setupForms(resDict);
    
    //----- recursively scan XObjects
    resDict->lookup("XObject", &xObjDict);
    if (xObjDict.isDict()) {
        for (i = 0; i < xObjDict.dictGetLength(); ++i) {
            
            // avoid infinite recursion on XObjects
            skip = gFalse;
            if ((xObjDict.dictGetValNF(i, &xObjRef)->isRef())) {
                ref0 = xObjRef.getRef();
                for (j = 0; j < xobjStack->getLength(); ++j) {
                    ref1 = *(Ref *)xobjStack->get(j);
                    if (ref1.num == ref0.num && ref1.gen == ref0.gen) {
                        skip = gTrue;
                        break;
                    }
                }
                if (!skip) {
                    xobjStack->append(&ref0);
                }
            }
            if (!skip) {
                
                // process the XObject's resource dictionary
                xObjDict.dictGetVal(i, &xObj);
                if (xObj.isStream()) {
                    xObj.streamGetDict()->lookup("Resources", &resObj);
                    if (resObj.isDict()) {
                        setupResources(resObj.getDict());
                    }
                    resObj.free();
                }
                xObj.free();
            }
            
            if (xObjRef.isRef() && !skip) {
                xobjStack->del(xobjStack->getLength() - 1);
            }
            xObjRef.free();
        }
    }
    xObjDict.free();
    
    //----- recursively scan Patterns
    resDict->lookup("Pattern", &patDict);
    if (patDict.isDict()) {
        inType3Char = gTrue;
        for (i = 0; i < patDict.dictGetLength(); ++i) {
            
            // avoid infinite recursion on Patterns
            skip = gFalse;
            if ((patDict.dictGetValNF(i, &patRef)->isRef())) {
                ref0 = patRef.getRef();
                for (j = 0; j < xobjStack->getLength(); ++j) {
                    ref1 = *(Ref *)xobjStack->get(j);
                    if (ref1.num == ref0.num && ref1.gen == ref0.gen) {
                        skip = gTrue;
                        break;
                    }
                }
                if (!skip) {
                    xobjStack->append(&ref0);
                }
            }
            if (!skip) {
                
                // process the Pattern's resource dictionary
                patDict.dictGetVal(i, &pat);
                if (pat.isStream()) {
                    pat.streamGetDict()->lookup("Resources", &resObj);
                    if (resObj.isDict()) {
                        setupResources(resObj.getDict());
                    }
                    resObj.free();
                }
                pat.free();
            }
            
            if (patRef.isRef() && !skip) {
                xobjStack->del(xobjStack->getLength() - 1);
            }
            patRef.free();
        }
        inType3Char = gFalse;
    }
    patDict.free();
}

void PSOutputDev::setupFonts(Dict *resDict) {
    Object obj1, obj2;
    Ref r;
    GfxFontDict *gfxFontDict;
    GfxFont *font;
    int i;
    
    gfxFontDict = NULL;
    resDict->lookupNF("Font", &obj1);
    if (obj1.isRef()) {
        obj1.fetch(xref, &obj2);
        if (obj2.isDict()) {
            r = obj1.getRef();
            gfxFontDict = new GfxFontDict(xref, &r, obj2.getDict());
        }
        obj2.free();
    } else if (obj1.isDict()) {
        gfxFontDict = new GfxFontDict(xref, NULL, obj1.getDict());
    }
    if (gfxFontDict) {
        for (i = 0; i < gfxFontDict->getNumFonts(); ++i) {
            if ((font = gfxFontDict->getFont(i))) {
                setupFont(font, resDict);
            }
        }
        delete gfxFontDict;
    }
    obj1.free();
}

void PSOutputDev::setupFont(GfxFont *font, Dict *parentResDict) {
    Ref fontFileID;
    GString *name;
    PSFontParam *fontParam;
    GString *psName;
    char buf[16];
    GBool subst;
    UnicodeMap *uMap;
    char *charName;
    double xs, ys;
    int code;
    double w1, w2;
    double *fm;
    int i, j;
    
    // check if font is already set up
    for (i = 0; i < fontIDLen; ++i) {
        if (fontIDs[i].num == font->getID()->num &&
            fontIDs[i].gen == font->getID()->gen) {
            return;
        }
    }
    
    // add entry to fontIDs list
    if (fontIDLen >= fontIDSize) {
        fontIDSize += 64;
        fontIDs = (Ref *)greallocn(fontIDs, fontIDSize, sizeof(Ref));
    }
    fontIDs[fontIDLen++] = *font->getID();
    
    xs = ys = 1;
    subst = gFalse;
    
    // check for resident 8-bit font
    if (font->getName() &&
        (fontParam = globalParams->getPSFont(font->getName()))) {
        psName = new GString(fontParam->psFontName->getCString());
        
        // check for embedded Type 1 font
    } else if (globalParams->getPSEmbedType1() &&
               font->getType() == fontType1 &&
               font->getEmbeddedFontID(&fontFileID)) {
        psName = filterPSName(font->getEmbeddedFontName());
        setupEmbeddedType1Font(&fontFileID, psName);
        
        // check for embedded Type 1C font
    } else if (globalParams->getPSEmbedType1() &&
               font->getType() == fontType1C &&
               font->getEmbeddedFontID(&fontFileID)) {
        // use the PDF font name because the embedded font name might
        // not include the subset prefix
        psName = filterPSName(font->getOrigName());
        setupEmbeddedType1CFont(font, &fontFileID, psName);
        
        // check for embedded OpenType - Type 1C font
    } else if (globalParams->getPSEmbedType1() &&
               font->getType() == fontType1COT &&
               font->getEmbeddedFontID(&fontFileID)) {
        // use the PDF font name because the embedded font name might
        // not include the subset prefix
        psName = filterPSName(font->getOrigName());
        setupEmbeddedOpenTypeT1CFont(font, &fontFileID, psName);
        
        // check for external Type 1 font file
    } else if (globalParams->getPSEmbedType1() &&
               font->getType() == fontType1 &&
               font->getExtFontFile()) {
        // this assumes that the PS font name matches the PDF font name
        psName = font->getName()->copy();
        setupExternalType1Font(font->getExtFontFile(), psName);
        
        // check for embedded TrueType font
    } else if (globalParams->getPSEmbedTrueType() &&
               (font->getType() == fontTrueType ||
                font->getType() == fontTrueTypeOT) &&
               font->getEmbeddedFontID(&fontFileID)) {
        psName = filterPSName(font->getEmbeddedFontName());
        setupEmbeddedTrueTypeFont(font, &fontFileID, psName);
        
        // check for external TrueType font file
    } else if (globalParams->getPSEmbedTrueType() &&
               font->getType() == fontTrueType &&
               font->getExtFontFile()) {
        psName = filterPSName(font->getName());
        setupExternalTrueTypeFont(font, psName);
        
        // check for embedded CID PostScript font
    } else if (globalParams->getPSEmbedCIDPostScript() &&
               font->getType() == fontCIDType0C &&
               font->getEmbeddedFontID(&fontFileID)) {
        psName = filterPSName(font->getEmbeddedFontName());
        setupEmbeddedCIDType0Font(font, &fontFileID, psName);
        
        // check for embedded CID TrueType font
    } else if (globalParams->getPSEmbedCIDTrueType() &&
               (font->getType() == fontCIDType2 ||
                font->getType() == fontCIDType2OT) &&
               font->getEmbeddedFontID(&fontFileID)) {
        psName = filterPSName(font->getEmbeddedFontName());
        //~ should check to see if font actually uses vertical mode
        setupEmbeddedCIDTrueTypeFont(font, &fontFileID, psName, gTrue);
        
        // check for embedded OpenType - CID CFF font
    } else if (globalParams->getPSEmbedCIDPostScript() &&
               font->getType() == fontCIDType0COT &&
               font->getEmbeddedFontID(&fontFileID)) {
        psName = filterPSName(font->getEmbeddedFontName());
        setupEmbeddedOpenTypeCFFFont(font, &fontFileID, psName);
        
        // check for Type 3 font
    } else if (font->getType() == fontType3) {
        psName = GString::format("T3_{0:d}_{1:d}",
                                 font->getID()->num, font->getID()->gen);
        setupType3Font(font, psName, parentResDict);
        
        // do 8-bit font substitution
    } else if (!font->isCIDFont()) {
        subst = gTrue;
        name = font->getName();
        psName = NULL;
        if (name) {
            for (i = 0; psFonts[i]; ++i) {
                if (name->cmp(psFonts[i]) == 0) {
                    psName = new GString(psFonts[i]);
                    break;
                }
            }
        }
        if (!psName) {
            if (font->isFixedWidth()) {
                i = 8;
            } else if (font->isSerif()) {
                i = 4;
            } else {
                i = 0;
            }
            if (font->isBold()) {
                i += 2;
            }
            if (font->isItalic()) {
                i += 1;
            }
            psName = new GString(psSubstFonts[i].psName);
            for (code = 0; code < 256; ++code) {
                if ((charName = ((Gfx8BitFont *)font)->getCharName(code)) &&
                    charName[0] == 'm' && charName[1] == '\0') {
                    break;
                }
            }
            if (code < 256) {
                w1 = ((Gfx8BitFont *)font)->getWidth(code);
            } else {
                w1 = 0;
            }
            w2 = psSubstFonts[i].mWidth;
            xs = w1 / w2;
            if (xs < 0.1) {
                xs = 1;
            }
            if (font->getType() == fontType3) {
                // This is a hack which makes it possible to substitute for some
                // Type 3 fonts.  The problem is that it's impossible to know what
                // the base coordinate system used in the font is without actually
                // rendering the font.
                ys = xs;
                fm = font->getFontMatrix();
                if (fm[0] != 0) {
                    ys *= fm[3] / fm[0];
                }
            } else {
                ys = 1;
            }
        }
        
        // do 16-bit font substitution
    } else if ((fontParam = globalParams->
                getPSFont16(font->getName(),
                            ((GfxCIDFont *)font)->getCollection(),
                            font->getWMode()))) {
                    subst = gTrue;
                    psName = fontParam->psFontName->copy();
                    if (font16EncLen >= font16EncSize) {
                        font16EncSize += 16;
                        font16Enc = (PSFont16Enc *)greallocn(font16Enc,
                                                             font16EncSize, sizeof(PSFont16Enc));
                    }
                    font16Enc[font16EncLen].fontID = *font->getID();
                    font16Enc[font16EncLen].enc = fontParam->encoding->copy();
                    if ((uMap = globalParams->getUnicodeMap(font16Enc[font16EncLen].enc))) {
                        uMap->decRefCnt();
                        ++font16EncLen;
                    } else {
                        error(-1, "Couldn't find Unicode map for 16-bit font encoding '%s'",
                              font16Enc[font16EncLen].enc->getCString());
                    }
                    
                    // give up - can't do anything with this font
                } else {
                    error(-1, "Couldn't find a font to substitute for '%s' ('%s' character collection)",
                          font->getName() ? font->getName()->getCString() : "(unnamed)",
                          ((GfxCIDFont *)font)->getCollection()
                          ? ((GfxCIDFont *)font)->getCollection()->getCString()
                          : "(unknown)");
                    return;
                }
    
    // generate PostScript code to set up the font
    if (font->isCIDFont()) {
        if (level == psLevel3 || level == psLevel3Sep) {
            writePSFmt("/F{0:d}_{1:d} /{2:t} {3:d} pdfMakeFont16L3\n",
                       font->getID()->num, font->getID()->gen, psName,
                       font->getWMode());
        } else {
            writePSFmt("/F{0:d}_{1:d} /{2:t} {3:d} pdfMakeFont16\n",
                       font->getID()->num, font->getID()->gen, psName,
                       font->getWMode());
        }
    } else {
        writePSFmt("/F{0:d}_{1:d} /{2:t} {3:.4g} {4:.4g}\n",
                   font->getID()->num, font->getID()->gen, psName, xs, ys);
        for (i = 0; i < 256; i += 8) {
            writePS((char *)((i == 0) ? "[ " : "  "));
            for (j = 0; j < 8; ++j) {
                if (font->getType() == fontTrueType &&
                    !subst &&
                    !((Gfx8BitFont *)font)->getHasEncoding()) {
                    sprintf(buf, "c%02x", i+j);
                    charName = buf;
                } else {
                    charName = ((Gfx8BitFont *)font)->getCharName(i+j);
                    // this is a kludge for broken PDF files that encode char 32
                    // as .notdef
                    if (i+j == 32 && charName && !strcmp(charName, ".notdef")) {
                        charName = "space";
                    }
                }
                writePS("/");
                writePSName(charName ? charName : (char *)".notdef");
                // the empty name is legal in PDF and PostScript, but PostScript
                // uses a double-slash (//...) for "immediately evaluated names",
                // so we need to add a space character here
                if (charName && !charName[0]) {
                    writePS(" ");
                }
            }
            writePS((i == 256-8) ? (char *)"]\n" : (char *)"\n");
        }
        writePS("pdfMakeFont\n");
    }
    
    delete psName;
}

void PSOutputDev::setupEmbeddedType1Font(Ref *id, GString *psName) {
    static char hexChar[17] = "0123456789abcdef";
    Object refObj, strObj, obj1, obj2, obj3;
    Dict *dict;
    int length1, length2, length3;
    int c;
    int start[4];
    GBool binMode;
    int i;
    
    // check if font is already embedded
    for (i = 0; i < fontFileIDLen; ++i) {
        if (fontFileIDs[i].num == id->num &&
            fontFileIDs[i].gen == id->gen)
            return;
    }
    
    // add entry to fontFileIDs list
    if (fontFileIDLen >= fontFileIDSize) {
        fontFileIDSize += 64;
        fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
    }
    fontFileIDs[fontFileIDLen++] = *id;
    
    // get the font stream and info
    refObj.initRef(id->num, id->gen);
    refObj.fetch(xref, &strObj);
    refObj.free();
    if (!strObj.isStream()) {
        error(-1, "Embedded font file object is not a stream");
        goto err1;
    }
    if (!(dict = strObj.streamGetDict())) {
        error(-1, "Embedded font stream is missing its dictionary");
        goto err1;
    }
    dict->lookup("Length1", &obj1);
    dict->lookup("Length2", &obj2);
    dict->lookup("Length3", &obj3);
    if (!obj1.isInt() || !obj2.isInt() || !obj3.isInt()) {
        error(-1, "Missing length fields in embedded font stream dictionary");
        obj1.free();
        obj2.free();
        obj3.free();
        goto err1;
    }
    length1 = obj1.getInt();
    length2 = obj2.getInt();
    length3 = obj3.getInt();
    obj1.free();
    obj2.free();
    obj3.free();
    
    // beginning comment
    writePSFmt("%%BeginResource: font {0:t}\n", psName);
    embFontList->append("%%+ font ");
    embFontList->append(psName->getCString());
    embFontList->append("\n");
    
    // copy ASCII portion of font
    strObj.streamReset();
    for (i = 0; i < length1 && (c = strObj.streamGetChar()) != EOF; ++i) {
        writePSChar(c);
    }
    
    // figure out if encrypted portion is binary or ASCII
    binMode = gFalse;
    for (i = 0; i < 4; ++i) {
        start[i] = strObj.streamGetChar();
        if (start[i] == EOF) {
            error(-1, "Unexpected end of file in embedded font stream");
            goto err1;
        }
        if (!((start[i] >= '0' && start[i] <= '9') ||
              (start[i] >= 'A' && start[i] <= 'F') ||
              (start[i] >= 'a' && start[i] <= 'f')))
            binMode = gTrue;
    }
    
    // convert binary data to ASCII
    if (binMode) {
        for (i = 0; i < 4; ++i) {
            writePSChar(hexChar[(start[i] >> 4) & 0x0f]);
            writePSChar(hexChar[start[i] & 0x0f]);
        }
#if 0 // this causes trouble for various PostScript printers
        // if Length2 is incorrect (too small), font data gets chopped, so
        // we take a few extra characters from the trailer just in case
        length2 += length3 >= 8 ? 8 : length3;
#endif
        while (i < length2) {
            if ((c = strObj.streamGetChar()) == EOF) {
                break;
            }
            writePSChar(hexChar[(c >> 4) & 0x0f]);
            writePSChar(hexChar[c & 0x0f]);
            if (++i % 32 == 0) {
                writePSChar('\n');
            }
        }
        if (i % 32 > 0) {
            writePSChar('\n');
        }
        
        // already in ASCII format -- just copy it
    } else {
        for (i = 0; i < 4; ++i) {
            writePSChar(start[i]);
        }
        for (i = 4; i < length2; ++i) {
            if ((c = strObj.streamGetChar()) == EOF) {
                break;
            }
            writePSChar(c);
        }
    }
    
    // write padding and "cleartomark"
    for (i = 0; i < 8; ++i) {
        writePS("00000000000000000000000000000000"
                "00000000000000000000000000000000\n");
    }
    writePS("cleartomark\n");
    
    // ending comment
    writePS("%%EndResource\n");
    
err1:
    strObj.streamClose();
    strObj.free();
}

//~ This doesn't handle .pfb files or binary eexec data (which only
//~ happens in pfb files?).
void PSOutputDev::setupExternalType1Font(GString *fileName, GString *psName) {
    FILE *fontFile;
    int c;
    int i;
    
    // check if font is already embedded
    for (i = 0; i < fontFileNameLen; ++i) {
        if (!fontFileNames[i]->cmp(fileName)) {
            return;
        }
    }
    
    // add entry to fontFileNames list
    if (fontFileNameLen >= fontFileNameSize) {
        fontFileNameSize += 64;
        fontFileNames = (GString **)greallocn(fontFileNames,
                                              fontFileNameSize, sizeof(GString *));
    }
    fontFileNames[fontFileNameLen++] = fileName->copy();
    
    // beginning comment
    writePSFmt("%%BeginResource: font {0:t}\n", psName);
    embFontList->append("%%+ font ");
    embFontList->append(psName->getCString());
    embFontList->append("\n");
    
    // copy the font file
    if (!(fontFile = fopen(fileName->getCString(), "rb"))) {
        error(-1, "Couldn't open external font file");
        return;
    }
    while ((c = fgetc(fontFile)) != EOF) {
        writePSChar(c);
    }
    fclose(fontFile);
    
    // ending comment
    writePS("%%EndResource\n");
}

void PSOutputDev::setupEmbeddedType1CFont(GfxFont *font, Ref *id,
                                          GString *psName) {
    char *fontBuf;
    int fontLen;
    FoFiType1C *ffT1C;
    int i;
    
    // check if font is already embedded
    for (i = 0; i < fontFileIDLen; ++i) {
        if (fontFileIDs[i].num == id->num &&
            fontFileIDs[i].gen == id->gen)
            return;
    }
    
    // add entry to fontFileIDs list
    if (fontFileIDLen >= fontFileIDSize) {
        fontFileIDSize += 64;
        fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
    }
    fontFileIDs[fontFileIDLen++] = *id;
    
    // beginning comment
    writePSFmt("%%BeginResource: font {0:t}\n", psName);
    embFontList->append("%%+ font ");
    embFontList->append(psName->getCString());
    embFontList->append("\n");
    
    // convert it to a Type 1 font
    fontBuf = font->readEmbFontFile(xref, &fontLen);
    if ((ffT1C = FoFiType1C::make(fontBuf, fontLen))) {
        ffT1C->convertToType1(psName->getCString(), NULL, gTrue,
                              outputFunc, outputStream);
        delete ffT1C;
    }
    gfree(fontBuf);
    
    // ending comment
    writePS("%%EndResource\n");
}

void PSOutputDev::setupEmbeddedOpenTypeT1CFont(GfxFont *font, Ref *id,
                                               GString *psName) {
    char *fontBuf;
    int fontLen;
    FoFiTrueType *ffTT;
    int i;
    
    // check if font is already embedded
    for (i = 0; i < fontFileIDLen; ++i) {
        if (fontFileIDs[i].num == id->num &&
            fontFileIDs[i].gen == id->gen)
            return;
    }
    
    // add entry to fontFileIDs list
    if (fontFileIDLen >= fontFileIDSize) {
        fontFileIDSize += 64;
        fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
    }
    fontFileIDs[fontFileIDLen++] = *id;
    
    // beginning comment
    writePSFmt("%%BeginResource: font {0:t}\n", psName);
    embFontList->append("%%+ font ");
    embFontList->append(psName->getCString());
    embFontList->append("\n");
    
    // convert it to a Type 1 font
    fontBuf = font->readEmbFontFile(xref, &fontLen);
    if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) {
        if (ffTT->isOpenTypeCFF()) {
            ffTT->convertToType1(psName->getCString(), NULL, gTrue,
                                 outputFunc, outputStream);
        }
        delete ffTT;
    }
    gfree(fontBuf);
    
    // ending comment
    writePS("%%EndResource\n");
}

void PSOutputDev::setupEmbeddedTrueTypeFont(GfxFont *font, Ref *id,
                                            GString *psName) {
    char *fontBuf;
    int fontLen;
    FoFiTrueType *ffTT;
    Gushort *codeToGID;
    int i;
    
    // check if font is already embedded
    for (i = 0; i < fontFileIDLen; ++i) {
        if (fontFileIDs[i].num == id->num &&
            fontFileIDs[i].gen == id->gen) {
            psName->appendf("_{0:d}", nextTrueTypeNum++);
            break;
        }
    }
    
    // add entry to fontFileIDs list
    if (i == fontFileIDLen) {
        if (fontFileIDLen >= fontFileIDSize) {
            fontFileIDSize += 64;
            fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
        }
        fontFileIDs[fontFileIDLen++] = *id;
    }
    
    // beginning comment
    writePSFmt("%%BeginResource: font {0:t}\n", psName);
    embFontList->append("%%+ font ");
    embFontList->append(psName->getCString());
    embFontList->append("\n");
    
    // convert it to a Type 42 font
    fontBuf = font->readEmbFontFile(xref, &fontLen);
    if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) {
        codeToGID = ((Gfx8BitFont *)font)->getCodeToGIDMap(ffTT);
        ffTT->convertToType42(psName->getCString(),
                              ((Gfx8BitFont *)font)->getHasEncoding()
                              ? ((Gfx8BitFont *)font)->getEncoding()
                              : (char **)NULL,
                              codeToGID, outputFunc, outputStream);
        if (codeToGID) {
            if (font8InfoLen >= font8InfoSize) {
                font8InfoSize += 16;
                font8Info = (PSFont8Info *)greallocn(font8Info,
                                                     font8InfoSize,
                                                     sizeof(PSFont8Info));
            }
            font8Info[font8InfoLen].fontID = *font->getID();
            font8Info[font8InfoLen].codeToGID = codeToGID;
            ++font8InfoLen;
        }
        delete ffTT;
    }
    gfree(fontBuf);
    
    // ending comment
    writePS("%%EndResource\n");
}

void PSOutputDev::setupExternalTrueTypeFont(GfxFont *font, GString *psName) {
    GString *fileName;
    char *fontBuf;
    int fontLen;
    FoFiTrueType *ffTT;
    Gushort *codeToGID;
    int i;
    
    // check if font is already embedded
    fileName = font->getExtFontFile();
    for (i = 0; i < fontFileNameLen; ++i) {
        if (!fontFileNames[i]->cmp(fileName)) {
            psName->appendf("_{0:d}", nextTrueTypeNum++);
            break;
        }
    }
    
    // add entry to fontFileNames list
    if (i == fontFileNameLen) {
        if (fontFileNameLen >= fontFileNameSize) {
            fontFileNameSize += 64;
            fontFileNames =
            (GString **)greallocn(fontFileNames,
                                  fontFileNameSize, sizeof(GString *));
        }
        fontFileNames[fontFileNameLen++] = fileName->copy();
    }
    
    // beginning comment
    writePSFmt("%%BeginResource: font {0:t}\n", psName);
    embFontList->append("%%+ font ");
    embFontList->append(psName->getCString());
    embFontList->append("\n");
    
    // convert it to a Type 42 font
    fontBuf = font->readExtFontFile(&fontLen);
    if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) {
        codeToGID = ((Gfx8BitFont *)font)->getCodeToGIDMap(ffTT);
        ffTT->convertToType42(psName->getCString(),
                              ((Gfx8BitFont *)font)->getHasEncoding()
                              ? ((Gfx8BitFont *)font)->getEncoding()
                              : (char **)NULL,
                              codeToGID, outputFunc, outputStream);
        if (codeToGID) {
            if (font8InfoLen >= font8InfoSize) {
                font8InfoSize += 16;
                font8Info = (PSFont8Info *)greallocn(font8Info,
                                                     font8InfoSize,
                                                     sizeof(PSFont8Info));
            }
            font8Info[font8InfoLen].fontID = *font->getID();
            font8Info[font8InfoLen].codeToGID = codeToGID;
            ++font8InfoLen;
        }
        delete ffTT;
    }
    gfree(fontBuf);
    
    // ending comment
    writePS("%%EndResource\n");
}

void PSOutputDev::setupEmbeddedCIDType0Font(GfxFont *font, Ref *id,
                                            GString *psName) {
    char *fontBuf;
    int fontLen;
    FoFiType1C *ffT1C;
    int i;
    
    // check if font is already embedded
    for (i = 0; i < fontFileIDLen; ++i) {
        if (fontFileIDs[i].num == id->num &&
            fontFileIDs[i].gen == id->gen)
            return;
    }
    
    // add entry to fontFileIDs list
    if (fontFileIDLen >= fontFileIDSize) {
        fontFileIDSize += 64;
        fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
    }
    fontFileIDs[fontFileIDLen++] = *id;
    
    // beginning comment
    writePSFmt("%%BeginResource: font {0:t}\n", psName);
    embFontList->append("%%+ font ");
    embFontList->append(psName->getCString());
    embFontList->append("\n");
    
    // convert it to a Type 0 font
    fontBuf = font->readEmbFontFile(xref, &fontLen);
    if ((ffT1C = FoFiType1C::make(fontBuf, fontLen))) {
        if (globalParams->getPSLevel() >= psLevel3) {
            // Level 3: use a CID font
            ffT1C->convertToCIDType0(psName->getCString(), outputFunc, outputStream);
        } else {
            // otherwise: use a non-CID composite font
            ffT1C->convertToType0(psName->getCString(), outputFunc, outputStream);
        }
        delete ffT1C;
    }
    gfree(fontBuf);
    
    // ending comment
    writePS("%%EndResource\n");
}

void PSOutputDev::setupEmbeddedCIDTrueTypeFont(GfxFont *font, Ref *id,
                                               GString *psName,
                                               GBool needVerticalMetrics) {
    char *fontBuf;
    int fontLen;
    FoFiTrueType *ffTT;
    int i;
    
    // check if font is already embedded
    for (i = 0; i < fontFileIDLen; ++i) {
        if (fontFileIDs[i].num == id->num &&
            fontFileIDs[i].gen == id->gen) {
            psName->appendf("_{0:d}", nextTrueTypeNum++);
            break;
        }
    }
    
    // add entry to fontFileIDs list
    if (fontFileIDLen >= fontFileIDSize) {
        fontFileIDSize += 64;
        fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
    }
    fontFileIDs[fontFileIDLen++] = *id;
    
    // beginning comment
    writePSFmt("%%BeginResource: font {0:t}\n", psName);
    embFontList->append("%%+ font ");
    embFontList->append(psName->getCString());
    embFontList->append("\n");
    
    // convert it to a Type 0 font
    fontBuf = font->readEmbFontFile(xref, &fontLen);
    if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) {
        if (globalParams->getPSLevel() >= psLevel3) {
            // Level 3: use a CID font
            ffTT->convertToCIDType2(psName->getCString(),
                                    ((GfxCIDFont *)font)->getCIDToGID(),
                                    ((GfxCIDFont *)font)->getCIDToGIDLen(),
                                    needVerticalMetrics,
                                    outputFunc, outputStream);
        } else {
            // otherwise: use a non-CID composite font
            ffTT->convertToType0(psName->getCString(),
                                 ((GfxCIDFont *)font)->getCIDToGID(),
                                 ((GfxCIDFont *)font)->getCIDToGIDLen(),
                                 needVerticalMetrics,
                                 outputFunc, outputStream);
        }
        delete ffTT;
    }
    gfree(fontBuf);
    
    // ending comment
    writePS("%%EndResource\n");
}

void PSOutputDev::setupEmbeddedOpenTypeCFFFont(GfxFont *font, Ref *id,
                                               GString *psName) {
    char *fontBuf;
    int fontLen;
    FoFiTrueType *ffTT;
    int i;
    
    // check if font is already embedded
    for (i = 0; i < fontFileIDLen; ++i) {
        if (fontFileIDs[i].num == id->num &&
            fontFileIDs[i].gen == id->gen)
            return;
    }
    
    // add entry to fontFileIDs list
    if (fontFileIDLen >= fontFileIDSize) {
        fontFileIDSize += 64;
        fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
    }
    fontFileIDs[fontFileIDLen++] = *id;
    
    // beginning comment
    writePSFmt("%%BeginResource: font {0:t}\n", psName);
    embFontList->append("%%+ font ");
    embFontList->append(psName->getCString());
    embFontList->append("\n");
    
    // convert it to a Type 0 font
    fontBuf = font->readEmbFontFile(xref, &fontLen);
    if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) {
        if (ffTT->isOpenTypeCFF()) {
            if (globalParams->getPSLevel() >= psLevel3) {
                // Level 3: use a CID font
                ffTT->convertToCIDType0(psName->getCString(),
                                        outputFunc, outputStream);
            } else {
                // otherwise: use a non-CID composite font
                ffTT->convertToType0(psName->getCString(), outputFunc, outputStream);
            }
        }
        delete ffTT;
    }
    gfree(fontBuf);
    
    // ending comment
    writePS("%%EndResource\n");
}

void PSOutputDev::setupType3Font(GfxFont *font, GString *psName,
                                 Dict *parentResDict) {
    Dict *resDict;
    Dict *charProcs;
    Object charProc;
    Gfx *gfx;
    PDFRectangle box;
    double *m;
    GString *buf;
    int i;
    
    // set up resources used by font
    if ((resDict = ((Gfx8BitFont *)font)->getResources())) {
        inType3Char = gTrue;
        setupResources(resDict);
        inType3Char = gFalse;
    } else {
        resDict = parentResDict;
    }
    
    // beginning comment
    writePSFmt("%%BeginResource: font {0:t}\n", psName);
    embFontList->append("%%+ font ");
    embFontList->append(psName->getCString());
    embFontList->append("\n");
    
    // font dictionary
    writePS("8 dict begin\n");
    writePS("/FontType 3 def\n");
    m = font->getFontMatrix();
    writePSFmt("/FontMatrix [{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g}] def\n",
               m[0], m[1], m[2], m[3], m[4], m[5]);
    m = font->getFontBBox();
    writePSFmt("/FontBBox [{0:.4g} {1:.4g} {2:.4g} {3:.4g}] def\n",
               m[0], m[1], m[2], m[3]);
    writePS("/Encoding 256 array def\n");
    writePS("  0 1 255 { Encoding exch /.notdef put } for\n");
    writePS("/BuildGlyph {\n");
    writePS("  exch /CharProcs get exch\n");
    writePS("  2 copy known not { pop /.notdef } if\n");
    writePS("  get exec\n");
    writePS("} bind def\n");
    writePS("/BuildChar {\n");
    writePS("  1 index /Encoding get exch get\n");
    writePS("  1 index /BuildGlyph get exec\n");
    writePS("} bind def\n");
    if ((charProcs = ((Gfx8BitFont *)font)->getCharProcs())) {
        writePSFmt("/CharProcs {0:d} dict def\n", charProcs->getLength());
        writePS("CharProcs begin\n");
        box.x1 = m[0];
        box.y1 = m[1];
        box.x2 = m[2];
        box.y2 = m[3];
        gfx = new Gfx(xref, this, resDict, &box, NULL);
        inType3Char = gTrue;
        for (i = 0; i < charProcs->getLength(); ++i) {
            t3Cacheable = gFalse;
            t3NeedsRestore = gFalse;
            writePS("/");
            writePSName(charProcs->getKey(i));
            writePS(" {\n");
            gfx->display(charProcs->getVal(i, &charProc));
            charProc.free();
            if (t3String) {
                if (t3Cacheable) {
                    buf = GString::format("{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g} setcachedevice\n",
                                          t3WX, t3WY, t3LLX, t3LLY, t3URX, t3URY);
                } else {
                    buf = GString::format("{0:.4g} {1:.4g} setcharwidth\n", t3WX, t3WY);
                }
                (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
                delete buf;
                (*outputFunc)(outputStream, t3String->getCString(),
                              t3String->getLength());
                delete t3String;
                t3String = NULL;
            }
            if (t3NeedsRestore) {
                (*outputFunc)(outputStream, "Q\n", 2);
            }
            writePS("} def\n");
        }
        inType3Char = gFalse;
        delete gfx;
        writePS("end\n");
    }
    writePS("currentdict end\n");
    writePSFmt("/{0:t} exch definefont pop\n", psName);
    
    // ending comment
    writePS("%%EndResource\n");
}

void PSOutputDev::setupImages(Dict *resDict) {
    Object xObjDict, xObj, xObjRef, subtypeObj;
    int i;
    
    if (!(mode == psModeForm || inType3Char || preload)) {
        return;
    }
    
    resDict->lookup("XObject", &xObjDict);
    if (xObjDict.isDict()) {
        for (i = 0; i < xObjDict.dictGetLength(); ++i) {
            xObjDict.dictGetValNF(i, &xObjRef);
            xObjDict.dictGetVal(i, &xObj);
            if (xObj.isStream()) {
                xObj.streamGetDict()->lookup("Subtype", &subtypeObj);
                if (subtypeObj.isName("Image")) {
                    if (xObjRef.isRef()) {
                        setupImage(xObjRef.getRef(), xObj.getStream());
                    } else {
                        error(-1, "Image in resource dict is not an indirect reference");
                    }
                }
                subtypeObj.free();
            }
            xObj.free();
            xObjRef.free();
        }
    }
    xObjDict.free();
}

void PSOutputDev::setupImage(Ref id, Stream *str) {
    GBool useRLE, useCompressed, useASCIIHex;
    GString *s;
    int c;
    int size, line, col, i;
    
    // check if image is already setup
    for (i = 0; i < imgIDLen; ++i) {
        if (imgIDs[i].num == id.num && imgIDs[i].gen == id.gen) {
            return;
        }
    }
    
    // add entry to imgIDs list
    if (imgIDLen >= imgIDSize) {
        if (imgIDSize == 0) {
            imgIDSize = 64;
        } else {
            imgIDSize *= 2;
        }
        imgIDs = (Ref *)greallocn(imgIDs, imgIDSize, sizeof(Ref));
    }
    imgIDs[imgIDLen++] = id;
    
    // filters
    //~ this does not correctly handle the DeviceN color space
    //~   -- need to use DeviceNRecoder
    if (level < psLevel2) {
        useRLE = gFalse;
        useCompressed = gFalse;
        useASCIIHex = gTrue;
    } else {
        s = str->getPSFilter(level < psLevel3 ? 2 : 3, "");
        if (s) {
            useRLE = gFalse;
            useCompressed = gTrue;
            delete s;
        } else {
            useRLE = gTrue;
            useCompressed = gFalse;
        }
        useASCIIHex = level == psLevel1 || level == psLevel1Sep ||
        globalParams->getPSASCIIHex();
    }
    if (useCompressed) {
        str = str->getUndecodedStream();
    }
    if (useRLE) {
        str = new RunLengthEncoder(str);
    }
    if (useASCIIHex) {
        str = new ASCIIHexEncoder(str);
    } else {
        str = new ASCII85Encoder(str);
    }
    
    // compute image data size
    str->reset();
    col = size = 0;
    do {
        do {
            c = str->getChar();
        } while (c == '\n' || c == '\r');
        if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
            break;
        }
        if (c == 'z') {
            ++col;
        } else {
            ++col;
            for (i = 1; i <= (useASCIIHex ? 1 : 4); ++i) {
                do {
                    c = str->getChar();
                } while (c == '\n' || c == '\r');
                if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
                    break;
                }
                ++col;
            }
        }
        if (col > 225) {
            ++size;
            col = 0;
        }
    } while (c != (useASCIIHex ? '>' : '~') && c != EOF);
    // add one entry for the final line of data; add another entry
    // because the RunLengthDecode filter may read past the end
    ++size;
    if (useRLE) {
        ++size;
    }
    writePSFmt("{0:d} array dup /ImData_{1:d}_{2:d} exch def\n",
               size, id.num, id.gen);
    str->close();
    
    // write the data into the array
    str->reset();
    line = col = 0;
    writePS((char *)(useASCIIHex ? "dup 0 <" : "dup 0 <~"));
    do {
        do {
            c = str->getChar();
        } while (c == '\n' || c == '\r');
        if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
            break;
        }
        if (c == 'z') {
            writePSChar(c);
            ++col;
        } else {
            writePSChar(c);
            ++col;
            for (i = 1; i <= (useASCIIHex ? 1 : 4); ++i) {
                do {
                    c = str->getChar();
                } while (c == '\n' || c == '\r');
                if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
                    break;
                }
                writePSChar(c);
                ++col;
            }
        }
        // each line is: "dup nnnnn <~...data...~> put<eol>"
        // so max data length = 255 - 20 = 235
        // chunks are 1 or 4 bytes each, so we have to stop at 232
        // but make it 225 just to be safe
        if (col > 225) {
            writePS((char *)(useASCIIHex ? "> put\n" : "~> put\n"));
            ++line;
            writePSFmt((char *)(useASCIIHex ? "dup {0:d} <" : "dup {0:d} <~"), line);
            col = 0;
        }
    } while (c != (useASCIIHex ? '>' : '~') && c != EOF);
    writePS((char *)(useASCIIHex ? "> put\n" : "~> put\n"));
    if (useRLE) {
        ++line;
        writePSFmt("{0:d} <> put\n", line);
    } else {
        writePS("pop\n");
    }
    str->close();
    
    delete str;
}

void PSOutputDev::setupForms(Dict *resDict) {
    Object xObjDict, xObj, xObjRef, subtypeObj;
    int i;
    
    if (!preload) {
        return;
    }
    
    resDict->lookup("XObject", &xObjDict);
    if (xObjDict.isDict()) {
        for (i = 0; i < xObjDict.dictGetLength(); ++i) {
            xObjDict.dictGetValNF(i, &xObjRef);
            xObjDict.dictGetVal(i, &xObj);
            if (xObj.isStream()) {
                xObj.streamGetDict()->lookup("Subtype", &subtypeObj);
                if (subtypeObj.isName("Form")) {
                    if (xObjRef.isRef()) {
                        setupForm(xObjRef.getRef(), &xObj);
                    } else {
                        error(-1, "Form in resource dict is not an indirect reference");
                    }
                }
                subtypeObj.free();
            }
            xObj.free();
            xObjRef.free();
        }
    }
    xObjDict.free();
}

void PSOutputDev::setupForm(Ref id, Object *strObj) {
    Dict *dict, *resDict;
    Object matrixObj, bboxObj, resObj, obj1;
    double m[6], bbox[4];
    PDFRectangle box;
    Gfx *gfx;
    int i;
    
    // check if form is already defined
    for (i = 0; i < formIDLen; ++i) {
        if (formIDs[i].num == id.num && formIDs[i].gen == id.gen) {
            return;
        }
    }
    
    // add entry to formIDs list
    if (formIDLen >= formIDSize) {
        if (formIDSize == 0) {
            formIDSize = 64;
        } else {
            formIDSize *= 2;
        }
        formIDs = (Ref *)greallocn(formIDs, formIDSize, sizeof(Ref));
    }
    formIDs[formIDLen++] = id;
    
    dict = strObj->streamGetDict();
    
    // get bounding box
    dict->lookup("BBox", &bboxObj);
    if (!bboxObj.isArray()) {
        bboxObj.free();
        error(-1, "Bad form bounding box");
        return;
    }
    for (i = 0; i < 4; ++i) {
        bboxObj.arrayGet(i, &obj1);
        bbox[i] = obj1.getNum();
        obj1.free();
    }
    bboxObj.free();
    
    // get matrix
    dict->lookup("Matrix", &matrixObj);
    if (matrixObj.isArray()) {
        for (i = 0; i < 6; ++i) {
            matrixObj.arrayGet(i, &obj1);
            m[i] = obj1.getNum();
            obj1.free();
        }
    } else {
        m[0] = 1; m[1] = 0;
        m[2] = 0; m[3] = 1;
        m[4] = 0; m[5] = 0;
    }
    matrixObj.free();
    
    // get resources
    dict->lookup("Resources", &resObj);
    resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL;
    
    writePSFmt("/f_{0:d}_{1:d} {{\n", id.num, id.gen);
    writePS("q\n");
    writePSFmt("[{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g}] cm\n",
               m[0], m[1], m[2], m[3], m[4], m[5]);
    
    box.x1 = bbox[0];
    box.y1 = bbox[1];
    box.x2 = bbox[2];
    box.y2 = bbox[3];
    gfx = new Gfx(xref, this, resDict, &box, &box);
    gfx->display(strObj);
    delete gfx;
    
    writePS("Q\n");
    writePS("} def\n");
    
    resObj.free();
}

GBool PSOutputDev::checkPageSlice(Page *page, double hDPI, double vDPI,
                                  int rotateA, GBool useMediaBox, GBool crop,
                                  int sliceX, int sliceY,
                                  int sliceW, int sliceH,
                                  GBool printing, Catalog *catalog,
                                  GBool (*abortCheckCbk)(void *data),
                                  void *abortCheckCbkData) {
#if HAVE_SPLASH
    PreScanOutputDev *scan;
    GBool rasterize;
    SplashOutputDev *splashOut;
    SplashColor paperColor;
    PDFRectangle box;
    GfxState *state;
    SplashBitmap *bitmap;
    Stream *str0, *str;
    Object obj;
    Guchar *p;
    Guchar col[4];
    double m0, m1, m2, m3, m4, m5;
    int c, w, h, x, y, comp, i;
    
    scan = new PreScanOutputDev();
    page->displaySlice(scan, 72, 72, rotateA, useMediaBox, crop,
                       sliceX, sliceY, sliceW, sliceH,
                       NULL, printing, catalog, abortCheckCbk, abortCheckCbkData);
    rasterize = scan->usesTransparency();
    delete scan;
    
    if (!rasterize) {
        return gTrue;
    }
    
    // rasterize the page
    if (level == psLevel1) {
        paperColor[0] = 0xff;
        splashOut = new SplashOutputDev(splashModeMono8, 1, gFalse,
                                        paperColor, gTrue, gFalse);
#if SPLASH_CMYK
    } else if (level == psLevel1Sep) {
        paperColor[0] = paperColor[1] = paperColor[2] = paperColor[3] = 0;
        splashOut = new SplashOutputDev(splashModeCMYK8, 1, gFalse,
                                        paperColor, gTrue, gFalse);
#endif
    } else {
        paperColor[0] = paperColor[1] = paperColor[2] = 0xff;
        splashOut = new SplashOutputDev(splashModeRGB8, 1, gFalse,
                                        paperColor, gTrue, gFalse);
    }
    splashOut->startDoc(xref);
    page->displaySlice(splashOut, splashDPI, splashDPI, rotateA,
                       useMediaBox, crop,
                       sliceX, sliceY, sliceW, sliceH,
                       NULL, printing, catalog, abortCheckCbk, abortCheckCbkData);
    
    // start the PS page
    page->makeBox(splashDPI, splashDPI, rotateA, useMediaBox, gFalse,
                  sliceX, sliceY, sliceW, sliceH, &box, &crop);
    rotateA += page->getRotate();
    if (rotateA >= 360) {
        rotateA -= 360;
    } else if (rotateA < 0) {
        rotateA += 360;
    }
    state = new GfxState(splashDPI, splashDPI, &box, rotateA, gFalse);
    startPage(page->getNum(), state);
    delete state;
    switch (rotateA) {
        case 0:
        default:  // this should never happen
            m0 = box.x2 - box.x1;
            m1 = 0;
            m2 = 0;
            m3 = box.y2 - box.y1;
            m4 = box.x1;
            m5 = box.y1;
            break;
        case 90:
            m0 = 0;
            m1 = box.y2 - box.y1;
            m2 = -(box.x2 - box.x1);
            m3 = 0;
            m4 = box.x2;
            m5 = box.y1;
            break;
        case 180:
            m0 = -(box.x2 - box.x1);
            m1 = 0;
            m2 = 0;
            m3 = -(box.y2 - box.y1);
            m4 = box.x2;
            m5 = box.y2;
            break;
        case 270:
            m0 = 0;
            m1 = -(box.y2 - box.y1);
            m2 = box.x2 - box.x1;
            m3 = 0;
            m4 = box.x1;
            m5 = box.y2;
            break;
    }
    
    //~ need to add the process colors
    
    // draw the rasterized image
    bitmap = splashOut->getBitmap();
    w = bitmap->getWidth();
    h = bitmap->getHeight();
    writePS("gsave\n");
    writePSFmt("[{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g}] concat\n",
               m0, m1, m2, m3, m4, m5);
    switch (level) {
        case psLevel1:
            writePSFmt("{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1\n",
                       w, h, w, -h, h);
            p = bitmap->getDataPtr();
            i = 0;
            for (y = 0; y < h; ++y) {
                for (x = 0; x < w; ++x) {
                    writePSFmt("{0:02x}", *p++);
                    if (++i == 32) {
                        writePSChar('\n');
                        i = 0;
                    }
                }
            }
            if (i != 0) {
                writePSChar('\n');
            }
            break;
        case psLevel1Sep:
            writePSFmt("{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1Sep\n",
                       w, h, w, -h, h);
            p = bitmap->getDataPtr();
            i = 0;
            col[0] = col[1] = col[2] = col[3] = 0;
            for (y = 0; y < h; ++y) {
                for (comp = 0; comp < 4; ++comp) {
                    for (x = 0; x < w; ++x) {
                        writePSFmt("{0:02x}", p[4*x + comp]);
                        col[comp] |= p[4*x + comp];
                        if (++i == 32) {
                            writePSChar('\n');
                            i = 0;
                        }
                    }
                }
                p += bitmap->getRowSize();
            }
            if (i != 0) {
                writePSChar('\n');
            }
            if (col[0]) {
                processColors |= psProcessCyan;
            }
            if (col[1]) {
                processColors |= psProcessMagenta;
            }
            if (col[2]) {
                processColors |= psProcessYellow;
            }
            if (col[3]) {
                processColors |= psProcessBlack;
            }
            break;
        case psLevel2:
        case psLevel2Sep:
        case psLevel3:
        case psLevel3Sep:
            writePS("/DeviceRGB setcolorspace\n");
            writePS("<<\n  /ImageType 1\n");
            writePSFmt("  /Width {0:d}\n", bitmap->getWidth());
            writePSFmt("  /Height {0:d}\n", bitmap->getHeight());
            writePSFmt("  /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n", w, -h, h);
            writePS("  /BitsPerComponent 8\n");
            writePS("  /Decode [0 1 0 1 0 1]\n");
            writePS("  /DataSource currentfile\n");
            if (globalParams->getPSASCIIHex()) {
                writePS("    /ASCIIHexDecode filter\n");
            } else {
                writePS("    /ASCII85Decode filter\n");
            }
            writePS("    /RunLengthDecode filter\n");
            writePS(">>\n");
            writePS("image\n");
            obj.initNull();
            str0 = new MemStream((char *)bitmap->getDataPtr(), 0, w * h * 3, &obj);
            str = new RunLengthEncoder(str0);
            if (globalParams->getPSASCIIHex()) {
                str = new ASCIIHexEncoder(str);
            } else {
                str = new ASCII85Encoder(str);
            }
            str->reset();
            while ((c = str->getChar()) != EOF) {
                writePSChar(c);
            }
            str->close();
            delete str;
            delete str0;
            processColors |= psProcessCMYK;
            break;
    }
    delete splashOut;
    writePS("grestore\n");
    
    // finish the PS page
    endPage();
    
    return gFalse;
#else
    return gTrue;
#endif
}

void PSOutputDev::startPage(int pageNum, GfxState *state) {
    int x1, y1, x2, y2, width, height;
    int imgWidth, imgHeight, imgWidth2, imgHeight2;
    GBool landscape;
    
    
    if (mode == psModePS) {
        writePSFmt("%%Page: {0:d} {1:d}\n", pageNum, seqPage);
        writePS("%%BeginPageSetup\n");
    }
    
    // underlays
    if (underlayCbk) {
        (*underlayCbk)(this, underlayCbkData);
    }
    if (overlayCbk) {
        saveState(NULL);
    }
    
    switch (mode) {
            
        case psModePS:
            // rotate, translate, and scale page
            imgWidth = imgURX - imgLLX;
            imgHeight = imgURY - imgLLY;
            x1 = (int)floor(state->getX1());
            y1 = (int)floor(state->getY1());
            x2 = (int)ceil(state->getX2());
            y2 = (int)ceil(state->getY2());
            width = x2 - x1;
            height = y2 - y1;
            tx = ty = 0;
            // rotation and portrait/landscape mode
            if (rotate0 >= 0) {
                rotate = (360 - rotate0) % 360;
                landscape = gFalse;
            } else {
                rotate = (360 - state->getRotate()) % 360;
                if (rotate == 0 || rotate == 180) {
                    if (width > height && width > imgWidth) {
                        rotate += 90;
                        landscape = gTrue;
                    } else {
                        landscape = gFalse;
                    }
                } else { // rotate == 90 || rotate == 270
                    if (height > width && height > imgWidth) {
                        rotate = 270 - rotate;
                        landscape = gTrue;
                    } else {
                        landscape = gFalse;
                    }
                }
            }
            writePSFmt("%%PageOrientation: {0:s}\n",
                       landscape ? "Landscape" : "Portrait");
            writePS("pdfStartPage\n");
            if (rotate == 0) {
                imgWidth2 = imgWidth;
                imgHeight2 = imgHeight;
            } else if (rotate == 90) {
                writePS("90 rotate\n");
                ty = -imgWidth;
                imgWidth2 = imgHeight;
                imgHeight2 = imgWidth;
            } else if (rotate == 180) {
                writePS("180 rotate\n");
                imgWidth2 = imgWidth;
                imgHeight2 = imgHeight;
                tx = -imgWidth;
                ty = -imgHeight;
            } else { // rotate == 270
                writePS("270 rotate\n");
                tx = -imgHeight;
                imgWidth2 = imgHeight;
                imgHeight2 = imgWidth;
            }
            // shrink or expand
            if (xScale0 > 0 && yScale0 > 0) {
                xScale = xScale0;
                yScale = yScale0;
            } else if ((globalParams->getPSShrinkLarger() &&
                        (width > imgWidth2 || height > imgHeight2)) ||
                       (globalParams->getPSExpandSmaller() &&
                        (width < imgWidth2 && height < imgHeight2))) {
                           xScale = (double)imgWidth2 / (double)width;
                           yScale = (double)imgHeight2 / (double)height;
                           if (yScale < xScale) {
                               xScale = yScale;
                           } else {
                               yScale = xScale;
                           }
                       } else {
                           xScale = yScale = 1;
                       }
            // deal with odd bounding boxes or clipping
            if (clipLLX0 < clipURX0 && clipLLY0 < clipURY0) {
                tx -= xScale * clipLLX0;
                ty -= yScale * clipLLY0;
            } else {
                tx -= xScale * x1;
                ty -= yScale * y1;
            }
            // center
            if (tx0 >= 0 && ty0 >= 0) {
                tx += rotate == 0 ? tx0 : ty0;
                ty += rotate == 0 ? ty0 : -tx0;
            } else if (globalParams->getPSCenter()) {
                if (clipLLX0 < clipURX0 && clipLLY0 < clipURY0) {
                    tx += (imgWidth2 - xScale * (clipURX0 - clipLLX0)) / 2;
                    ty += (imgHeight2 - yScale * (clipURY0 - clipLLY0)) / 2;
                } else {
                    tx += (imgWidth2 - xScale * width) / 2;
                    ty += (imgHeight2 - yScale * height) / 2;
                }
            }
            tx += rotate == 0 ? imgLLX : imgLLY;
            ty += rotate == 0 ? imgLLY : -imgLLX;
            if (tx != 0 || ty != 0) {
                writePSFmt("{0:.4g} {1:.4g} translate\n", tx, ty);
            }
            if (xScale != 1 || yScale != 1) {
                writePSFmt("{0:.4f} {0:.4f} scale\n", xScale);
            }
            if (clipLLX0 < clipURX0 && clipLLY0 < clipURY0) {
                writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} re W\n",
                           clipLLX0, clipLLY0, clipURX0 - clipLLX0, clipURY0 - clipLLY0);
            } else {
                writePSFmt("{0:d} {1:d} {2:d} {3:d} re W\n", x1, y1, x2 - x1, y2 - y1);
            }
            
            writePS("%%EndPageSetup\n");
            ++seqPage;
            break;
            
        case psModeEPS:
            writePS("pdfStartPage\n");
            tx = ty = 0;
            rotate = (360 - state->getRotate()) % 360;
            if (rotate == 0) {
            } else if (rotate == 90) {
                writePS("90 rotate\n");
                tx = -epsX1;
                ty = -epsY2;
            } else if (rotate == 180) {
                writePS("180 rotate\n");
                tx = -(epsX1 + epsX2);
                ty = -(epsY1 + epsY2);
            } else { // rotate == 270
                writePS("270 rotate\n");
                tx = -epsX2;
                ty = -epsY1;
            }
            if (tx != 0 || ty != 0) {
                writePSFmt("{0:.4g} {1:.4g} translate\n", tx, ty);
            }
            xScale = yScale = 1;
            break;
            
        case psModeForm:
            writePS("/PaintProc {\n");
            writePS("begin xpdf begin\n");
            writePS("pdfStartPage\n");
            tx = ty = 0;
            xScale = yScale = 1;
            rotate = 0;
            break;
    }
}

void PSOutputDev::endPage() {
    if (overlayCbk) {
        restoreState(NULL);
        (*overlayCbk)(this, overlayCbkData);
    }
    
    
    if (mode == psModeForm) {
        writePS("pdfEndPage\n");
        writePS("end end\n");
        writePS("} def\n");
        writePS("end end\n");
    } else {
        if (!manualCtrl) {
            writePS("showpage\n");
        }
        writePS("%%PageTrailer\n");
        writePageTrailer();
    }
}

void PSOutputDev::saveState(GfxState *state) {
    writePS("q\n");
    ++numSaves;
}

void PSOutputDev::restoreState(GfxState *state) {
    writePS("Q\n");
    --numSaves;
}

void PSOutputDev::updateCTM(GfxState *state, double m11, double m12,
                            double m21, double m22, double m31, double m32) {
    writePSFmt("[{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g}] cm\n",
               m11, m12, m21, m22, m31, m32);
}

void PSOutputDev::updateLineDash(GfxState *state) {
    double *dash;
    double start;
    int length, i;
    
    state->getLineDash(&dash, &length, &start);
    writePS("[");
    for (i = 0; i < length; ++i) {
        writePSFmt("{0:.4g}{1:w}",
                   dash[i] < 0 ? 0 : dash[i],
                   (i == length-1) ? 0 : 1);
    }
    writePSFmt("] {0:.4g} d\n", start);
}

void PSOutputDev::updateFlatness(GfxState *state) {
    writePSFmt("{0:d} i\n", state->getFlatness());
}

void PSOutputDev::updateLineJoin(GfxState *state) {
    writePSFmt("{0:d} j\n", state->getLineJoin());
}

void PSOutputDev::updateLineCap(GfxState *state) {
    writePSFmt("{0:d} J\n", state->getLineCap());
}

void PSOutputDev::updateMiterLimit(GfxState *state) {
    writePSFmt("{0:.4g} M\n", state->getMiterLimit());
}

void PSOutputDev::updateLineWidth(GfxState *state) {
    writePSFmt("{0:.4g} w\n", state->getLineWidth());
}

void PSOutputDev::updateFillColorSpace(GfxState *state) {
    switch (level) {
        case psLevel1:
        case psLevel1Sep:
            break;
        case psLevel2:
        case psLevel3:
            if (state->getFillColorSpace()->getMode() != csPattern) {
                dumpColorSpaceL2(state->getFillColorSpace(), gTrue, gFalse, gFalse);
                writePS(" cs\n");
            }
            break;
        case psLevel2Sep:
        case psLevel3Sep:
            break;
    }
}

void PSOutputDev::updateStrokeColorSpace(GfxState *state) {
    switch (level) {
        case psLevel1:
        case psLevel1Sep:
            break;
        case psLevel2:
        case psLevel3:
            if (state->getStrokeColorSpace()->getMode() != csPattern) {
                dumpColorSpaceL2(state->getStrokeColorSpace(), gTrue, gFalse, gFalse);
                writePS(" CS\n");
            }
            break;
        case psLevel2Sep:
        case psLevel3Sep:
            break;
    }
}

void PSOutputDev::updateFillColor(GfxState *state) {
    GfxColor color;
    GfxColor *colorPtr;
    GfxGray gray;
    GfxCMYK cmyk;
    GfxSeparationColorSpace *sepCS;
    double c, m, y, k;
    int i;
    
    switch (level) {
        case psLevel1:
            state->getFillGray(&gray);
            writePSFmt("{0:.4g} g\n", colToDbl(gray));
            break;
        case psLevel1Sep:
            state->getFillCMYK(&cmyk);
            c = colToDbl(cmyk.c);
            m = colToDbl(cmyk.m);
            y = colToDbl(cmyk.y);
            k = colToDbl(cmyk.k);
            writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} k\n", c, m, y, k);
            addProcessColor(c, m, y, k);
            break;
        case psLevel2:
        case psLevel3:
            if (state->getFillColorSpace()->getMode() != csPattern) {
                colorPtr = state->getFillColor();
                writePS("[");
                for (i = 0; i < state->getFillColorSpace()->getNComps(); ++i) {
                    if (i > 0) {
                        writePS(" ");
                    }
                    writePSFmt("{0:.4g}", colToDbl(colorPtr->c[i]));
                }
                writePS("] sc\n");
            }
            break;
        case psLevel2Sep:
        case psLevel3Sep:
            if (state->getFillColorSpace()->getMode() == csSeparation) {
                sepCS = (GfxSeparationColorSpace *)state->getFillColorSpace();
                color.c[0] = gfxColorComp1;
                sepCS->getCMYK(&color, &cmyk);
                writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} ({5:t}) ck\n",
                           colToDbl(state->getFillColor()->c[0]),
                           colToDbl(cmyk.c), colToDbl(cmyk.m),
                           colToDbl(cmyk.y), colToDbl(cmyk.k),
                           sepCS->getName());
                addCustomColor(sepCS);
            } else {
                state->getFillCMYK(&cmyk);
                c = colToDbl(cmyk.c);
                m = colToDbl(cmyk.m);
                y = colToDbl(cmyk.y);
                k = colToDbl(cmyk.k);
                writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} k\n", c, m, y, k);
                addProcessColor(c, m, y, k);
            }
            break;
    }
    t3Cacheable = gFalse;
}

void PSOutputDev::updateStrokeColor(GfxState *state) {
    GfxColor color;
    GfxColor *colorPtr;
    GfxGray gray;
    GfxCMYK cmyk;
    GfxSeparationColorSpace *sepCS;
    double c, m, y, k;
    int i;
    
    switch (level) {
        case psLevel1:
            state->getStrokeGray(&gray);
            writePSFmt("{0:.4g} G\n", colToDbl(gray));
            break;
        case psLevel1Sep:
            state->getStrokeCMYK(&cmyk);
            c = colToDbl(cmyk.c);
            m = colToDbl(cmyk.m);
            y = colToDbl(cmyk.y);
            k = colToDbl(cmyk.k);
            writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} K\n", c, m, y, k);
            addProcessColor(c, m, y, k);
            break;
        case psLevel2:
        case psLevel3:
            if (state->getStrokeColorSpace()->getMode() != csPattern) {
                colorPtr = state->getStrokeColor();
                writePS("[");
                for (i = 0; i < state->getStrokeColorSpace()->getNComps(); ++i) {
                    if (i > 0) {
                        writePS(" ");
                    }
                    writePSFmt("{0:.4g}", colToDbl(colorPtr->c[i]));
                }
                writePS("] SC\n");
            }
            break;
        case psLevel2Sep:
        case psLevel3Sep:
            if (state->getStrokeColorSpace()->getMode() == csSeparation) {
                sepCS = (GfxSeparationColorSpace *)state->getStrokeColorSpace();
                color.c[0] = gfxColorComp1;
                sepCS->getCMYK(&color, &cmyk);
                writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} ({5:t}) CK\n",
                           colToDbl(state->getStrokeColor()->c[0]),
                           colToDbl(cmyk.c), colToDbl(cmyk.m),
                           colToDbl(cmyk.y), colToDbl(cmyk.k),
                           sepCS->getName());
                addCustomColor(sepCS);
            } else {
                state->getStrokeCMYK(&cmyk);
                c = colToDbl(cmyk.c);
                m = colToDbl(cmyk.m);
                y = colToDbl(cmyk.y);
                k = colToDbl(cmyk.k);
                writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} K\n", c, m, y, k);
                addProcessColor(c, m, y, k);
            }
            break;
    }
    t3Cacheable = gFalse;
}

void PSOutputDev::addProcessColor(double c, double m, double y, double k) {
    if (c > 0) {
        processColors |= psProcessCyan;
    }
    if (m > 0) {
        processColors |= psProcessMagenta;
    }
    if (y > 0) {
        processColors |= psProcessYellow;
    }
    if (k > 0) {
        processColors |= psProcessBlack;
    }
}

void PSOutputDev::addCustomColor(GfxSeparationColorSpace *sepCS) {
    PSOutCustomColor *cc;
    GfxColor color;
    GfxCMYK cmyk;
    
    for (cc = customColors; cc; cc = cc->next) {
        if (!cc->name->cmp(sepCS->getName())) {
            return;
        }
    }
    color.c[0] = gfxColorComp1;
    sepCS->getCMYK(&color, &cmyk);
    cc = new PSOutCustomColor(colToDbl(cmyk.c), colToDbl(cmyk.m),
                              colToDbl(cmyk.y), colToDbl(cmyk.k),
                              sepCS->getName()->copy());
    cc->next = customColors;
    customColors = cc;
}

void PSOutputDev::updateFillOverprint(GfxState *state) {
    if (level >= psLevel2) {
        writePSFmt("{0:s} op\n", state->getFillOverprint() ? "true" : "false");
    }
}

void PSOutputDev::updateStrokeOverprint(GfxState *state) {
    if (level >= psLevel2) {
        writePSFmt("{0:s} OP\n", state->getStrokeOverprint() ? "true" : "false");
    }
}

void PSOutputDev::updateTransfer(GfxState *state) {
    Function **funcs;
    int i;
    
    funcs = state->getTransfer();
    if (funcs[0] && funcs[1] && funcs[2] && funcs[3]) {
        if (level >= psLevel2) {
            for (i = 0; i < 4; ++i) {
                cvtFunction(funcs[i]);
            }
            writePS("setcolortransfer\n");
        } else {
            cvtFunction(funcs[3]);
            writePS("settransfer\n");
        }
    } else if (funcs[0]) {
        cvtFunction(funcs[0]);
        writePS("settransfer\n");
    } else {
        writePS("{} settransfer\n");
    }
}

void PSOutputDev::updateFont(GfxState *state) {
    if (state->getFont()) {
        writePSFmt("/F{0:d}_{1:d} {2:.4g} Tf\n",
                   state->getFont()->getID()->num, state->getFont()->getID()->gen,
                   fabs(state->getFontSize()) < 0.00001 ? 0.00001
                   : state->getFontSize());
    }
}

void PSOutputDev::updateTextMat(GfxState *state) {
    double *mat;
    
    mat = state->getTextMat();
    if (fabs(mat[0] * mat[3] - mat[1] * mat[2]) < 0.00001) {
        // avoid a singular (or close-to-singular) matrix
        writePSFmt("[0.00001 0 0 0.00001 {0:.4g} {1:.4g}] Tm\n", mat[4], mat[5]);
    } else {
        writePSFmt("[{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g}] Tm\n",
                   mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]);
    }
}

void PSOutputDev::updateCharSpace(GfxState *state) {
    writePSFmt("{0:.4g} Tc\n", state->getCharSpace());
}

void PSOutputDev::updateRender(GfxState *state) {
    int rm;
    
    rm = state->getRender();
    writePSFmt("{0:d} Tr\n", rm);
    rm &= 3;
    if (rm != 0 && rm != 3) {
        t3Cacheable = gFalse;
    }
}

void PSOutputDev::updateRise(GfxState *state) {
    writePSFmt("{0:.4g} Ts\n", state->getRise());
}

void PSOutputDev::updateWordSpace(GfxState *state) {
    writePSFmt("{0:.4g} Tw\n", state->getWordSpace());
}

void PSOutputDev::updateHorizScaling(GfxState *state) {
    double h;
    
    h = state->getHorizScaling();
    if (fabs(h) < 0.01) {
        h = 0.01;
    }
    writePSFmt("{0:.4g} Tz\n", h);
}

void PSOutputDev::updateTextPos(GfxState *state) {
    writePSFmt("{0:.4g} {1:.4g} Td\n", state->getLineX(), state->getLineY());
}

void PSOutputDev::updateTextShift(GfxState *state, double shift) {
    if (state->getFont()->getWMode()) {
        writePSFmt("{0:.4g} TJmV\n", shift);
    } else {
        writePSFmt("{0:.4g} TJm\n", shift);
    }
}

void PSOutputDev::stroke(GfxState *state) {
    doPath(state->getPath());
    if (t3String) {
        // if we're construct a cacheable Type 3 glyph, we need to do
        // everything in the fill color
        writePS("Sf\n");
    } else {
        writePS("S\n");
    }
}

void PSOutputDev::fill(GfxState *state) {
    doPath(state->getPath());
    writePS("f\n");
}

void PSOutputDev::eoFill(GfxState *state) {
    doPath(state->getPath());
    writePS("f*\n");
}

void PSOutputDev::tilingPatternFill(GfxState *state, Object *str,
                                    int paintType, Dict *resDict,
                                    double *mat, double *bbox,
                                    int x0, int y0, int x1, int y1,
                                    double xStep, double yStep) {
    PDFRectangle box;
    Gfx *gfx;
    
    // define a Type 3 font
    writePS("8 dict begin\n");
    writePS("/FontType 3 def\n");
    writePS("/FontMatrix [1 0 0 1 0 0] def\n");
    writePSFmt("/FontBBox [{0:.4g} {1:.4g} {2:.4g} {3:.4g}] def\n",
               bbox[0], bbox[1], bbox[2], bbox[3]);
    writePS("/Encoding 256 array def\n");
    writePS("  0 1 255 { Encoding exch /.notdef put } for\n");
    writePS("  Encoding 120 /x put\n");
    writePS("/BuildGlyph {\n");
    writePS("  exch /CharProcs get exch\n");
    writePS("  2 copy known not { pop /.notdef } if\n");
    writePS("  get exec\n");
    writePS("} bind def\n");
    writePS("/BuildChar {\n");
    writePS("  1 index /Encoding get exch get\n");
    writePS("  1 index /BuildGlyph get exec\n");
    writePS("} bind def\n");
    writePS("/CharProcs 1 dict def\n");
    writePS("CharProcs begin\n");
    box.x1 = bbox[0];
    box.y1 = bbox[1];
    box.x2 = bbox[2];
    box.y2 = bbox[3];
    gfx = new Gfx(xref, this, resDict, &box, NULL);
    writePS("/x {\n");
    if (paintType == 2) {
        writePSFmt("{0:.4g} 0 {1:.4g} {2:.4g} {3:.4g} {4:.4g} setcachedevice\n",
                   xStep, bbox[0], bbox[1], bbox[2], bbox[3]);
    } else {
        if (x1 - 1 <= x0) {
            writePS("1 0 setcharwidth\n");
        } else {
            writePSFmt("{0:.4g} 0 setcharwidth\n", xStep);
        }
    }
    inType3Char = gTrue;
    ++numTilingPatterns;
    gfx->display(str);
    --numTilingPatterns;
    inType3Char = gFalse;
    writePS("} def\n");
    delete gfx;
    writePS("end\n");
    writePS("currentdict end\n");
    writePSFmt("/xpdfTile{0:d} exch definefont pop\n", numTilingPatterns);
    
    // draw the tiles
    writePSFmt("/xpdfTile{0:d} findfont setfont\n", numTilingPatterns);
    writePSFmt("gsave [{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g}] concat\n",
               mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]);
    writePSFmt("{0:d} 1 {1:d} {{ {2:.4g} exch {3:.4g} mul m {4:d} 1 {5:d} {{ pop (x) show }} for }} for\n",
               y0, y1 - 1, x0 * xStep, yStep, x0, x1 - 1);
    writePS("grestore\n");
}

GBool PSOutputDev::functionShadedFill(GfxState *state,
                                      GfxFunctionShading *shading) {
    double x0, y0, x1, y1;
    double *mat;
    int i;
    
    if (level == psLevel2Sep || level == psLevel3Sep) {
        if (shading->getColorSpace()->getMode() != csDeviceCMYK) {
            return gFalse;
        }
        processColors |= psProcessCMYK;
    }
    
    shading->getDomain(&x0, &y0, &x1, &y1);
    mat = shading->getMatrix();
    writePSFmt("/mat [{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g}] def\n",
               mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]);
    writePSFmt("/n {0:d} def\n", shading->getColorSpace()->getNComps());
    if (shading->getNFuncs() == 1) {
        writePS("/func ");
        cvtFunction(shading->getFunc(0));
        writePS("def\n");
    } else {
        writePS("/func {\n");
        for (i = 0; i < shading->getNFuncs(); ++i) {
            if (i < shading->getNFuncs() - 1) {
                writePS("2 copy\n");
            }
            cvtFunction(shading->getFunc(i));
            writePS("exec\n");
            if (i < shading->getNFuncs() - 1) {
                writePS("3 1 roll\n");
            }
        }
        writePS("} def\n");
    }
    writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} 0 funcSH\n", x0, y0, x1, y1);
    
    return gTrue;
}

GBool PSOutputDev::axialShadedFill(GfxState *state, GfxAxialShading *shading) {
    double xMin, yMin, xMax, yMax;
    double x0, y0, x1, y1, dx, dy, mul;
    double tMin, tMax, t, t0, t1;
    int i;
    
    if (level == psLevel2Sep || level == psLevel3Sep) {
        if (shading->getColorSpace()->getMode() != csDeviceCMYK) {
            return gFalse;
        }
        processColors |= psProcessCMYK;
    }
    
    // get the clip region bbox
    state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
    
    // compute min and max t values, based on the four corners of the
    // clip region bbox
    shading->getCoords(&x0, &y0, &x1, &y1);
    dx = x1 - x0;
    dy = y1 - y0;
    if (fabs(dx) < 0.01 && fabs(dy) < 0.01) {
        return gTrue;
    } else {
        mul = 1 / (dx * dx + dy * dy);
        tMin = tMax = ((xMin - x0) * dx + (yMin - y0) * dy) * mul;
        t = ((xMin - x0) * dx + (yMax - y0) * dy) * mul;
        if (t < tMin) {
            tMin = t;
        } else if (t > tMax) {
            tMax = t;
        }
        t = ((xMax - x0) * dx + (yMin - y0) * dy) * mul;
        if (t < tMin) {
            tMin = t;
        } else if (t > tMax) {
            tMax = t;
        }
        t = ((xMax - x0) * dx + (yMax - y0) * dy) * mul;
        if (t < tMin) {
            tMin = t;
        } else if (t > tMax) {
            tMax = t;
        }
        if (tMin < 0 && !shading->getExtend0()) {
            tMin = 0;
        }
        if (tMax > 1 && !shading->getExtend1()) {
            tMax = 1;
        }
    }
    
    // get the function domain
    t0 = shading->getDomain0();
    t1 = shading->getDomain1();
    
    // generate the PS code
    writePSFmt("/t0 {0:.4g} def\n", t0);
    writePSFmt("/t1 {0:.4g} def\n", t1);
    writePSFmt("/dt {0:.4g} def\n", t1 - t0);
    writePSFmt("/x0 {0:.4g} def\n", x0);
    writePSFmt("/y0 {0:.4g} def\n", y0);
    writePSFmt("/dx {0:.4g} def\n", x1 - x0);
    writePSFmt("/x1 {0:.4g} def\n", x1);
    writePSFmt("/y1 {0:.4g} def\n", y1);
    writePSFmt("/dy {0:.4g} def\n", y1 - y0);
    writePSFmt("/xMin {0:.4g} def\n", xMin);
    writePSFmt("/yMin {0:.4g} def\n", yMin);
    writePSFmt("/xMax {0:.4g} def\n", xMax);
    writePSFmt("/yMax {0:.4g} def\n", yMax);
    writePSFmt("/n {0:d} def\n", shading->getColorSpace()->getNComps());
    if (shading->getNFuncs() == 1) {
        writePS("/func ");
        cvtFunction(shading->getFunc(0));
        writePS("def\n");
    } else {
        writePS("/func {\n");
        for (i = 0; i < shading->getNFuncs(); ++i) {
            if (i < shading->getNFuncs() - 1) {
                writePS("dup\n");
            }
            cvtFunction(shading->getFunc(i));
            writePS("exec\n");
            if (i < shading->getNFuncs() - 1) {
                writePS("exch\n");
            }
        }
        writePS("} def\n");
    }
    writePSFmt("{0:.4g} {1:.4g} 0 axialSH\n", tMin, tMax);
    
    return gTrue;
}

GBool PSOutputDev::radialShadedFill(GfxState *state,
                                    GfxRadialShading *shading) {
    double xMin, yMin, xMax, yMax;
    double x0, y0, r0, x1, y1, r1, t0, t1;
    double xa, ya, ra;
    double sz, xz, yz, sMin, sMax, sa, ta;
    double theta, alpha, a1, a2;
    GBool enclosed;
    int i;
    
    if (level == psLevel2Sep || level == psLevel3Sep) {
        if (shading->getColorSpace()->getMode() != csDeviceCMYK) {
            return gFalse;
        }
        processColors |= psProcessCMYK;
    }
    
    // get the shading info
    shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1);
    t0 = shading->getDomain0();
    t1 = shading->getDomain1();
    
    // Compute the point at which r(s) = 0; check for the enclosed
    // circles case; and compute the angles for the tangent lines.
    if (r0 == r1) {
        enclosed = x0 == x1 && y0 == y1;
        theta = 0;
        sz = 0; // make gcc happy
    } else {
        sz = -r0 / (r1 - r0);
        xz = x0 + sz * (x1 - x0);
        yz = y0 + sz * (y1 - y0);
        enclosed = (xz - x0) * (xz - x0) + (yz - y0) * (yz - y0) <= r0 * r0;
        theta = asin(r0 / sqrt((x0 - xz) * (x0 - xz) + (y0 - yz) * (y0 - yz)));
        if (r0 > r1) {
            theta = -theta;
        }
    }
    if (enclosed) {
        a1 = 0;
        a2 = 360;
    } else {
        alpha = atan2(y1 - y0, x1 - x0);
        a1 = (180 / M_PI) * (alpha + theta) + 90;
        a2 = (180 / M_PI) * (alpha - theta) - 90;
        while (a2 < a1) {
            a2 += 360;
        }
    }
    
    // compute the (possibly extended) s range
    state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
    if (enclosed) {
        sMin = 0;
        sMax = 1;
    } else {
        sMin = 1;
        sMax = 0;
        // solve for x(s) + r(s) = xMin
        if ((x1 + r1) - (x0 + r0) != 0) {
            sa = (xMin - (x0 + r0)) / ((x1 + r1) - (x0 + r0));
            if (sa < sMin) {
                sMin = sa;
            } else if (sa > sMax) {
                sMax = sa;
            }
        }
        // solve for x(s) - r(s) = xMax
        if ((x1 - r1) - (x0 - r0) != 0) {
            sa = (xMax - (x0 - r0)) / ((x1 - r1) - (x0 - r0));
            if (sa < sMin) {
                sMin = sa;
            } else if (sa > sMax) {
                sMax = sa;
            }
        }
        // solve for y(s) + r(s) = yMin
        if ((y1 + r1) - (y0 + r0) != 0) {
            sa = (yMin - (y0 + r0)) / ((y1 + r1) - (y0 + r0));
            if (sa < sMin) {
                sMin = sa;
            } else if (sa > sMax) {
                sMax = sa;
            }
        }
        // solve for y(s) - r(s) = yMax
        if ((y1 - r1) - (y0 - r0) != 0) {
            sa = (yMax - (y0 - r0)) / ((y1 - r1) - (y0 - r0));
            if (sa < sMin) {
                sMin = sa;
            } else if (sa > sMax) {
                sMax = sa;
            }
        }
        // check against sz
        if (r0 < r1) {
            if (sMin < sz) {
                sMin = sz;
            }
        } else if (r0 > r1) {
            if (sMax > sz) {
                sMax = sz;
            }
        }
        // check the 'extend' flags
        if (!shading->getExtend0() && sMin < 0) {
            sMin = 0;
        }
        if (!shading->getExtend1() && sMax > 1) {
            sMax = 1;
        }
    }
    
    // generate the PS code
    writePSFmt("/x0 {0:.4g} def\n", x0);
    writePSFmt("/x1 {0:.4g} def\n", x1);
    writePSFmt("/dx {0:.4g} def\n", x1 - x0);
    writePSFmt("/y0 {0:.4g} def\n", y0);
    writePSFmt("/y1 {0:.4g} def\n", y1);
    writePSFmt("/dy {0:.4g} def\n", y1 - y0);
    writePSFmt("/r0 {0:.4g} def\n", r0);
    writePSFmt("/r1 {0:.4g} def\n", r1);
    writePSFmt("/dr {0:.4g} def\n", r1 - r0);
    writePSFmt("/t0 {0:.4g} def\n", t0);
    writePSFmt("/t1 {0:.4g} def\n", t1);
    writePSFmt("/dt {0:.4g} def\n", t1 - t0);
    writePSFmt("/n {0:d} def\n", shading->getColorSpace()->getNComps());
    writePSFmt("/encl {0:s} def\n", enclosed ? "true" : "false");
    writePSFmt("/a1 {0:.4g} def\n", a1);
    writePSFmt("/a2 {0:.4g} def\n", a2);
    if (shading->getNFuncs() == 1) {
        writePS("/func ");
        cvtFunction(shading->getFunc(0));
        writePS("def\n");
    } else {
        writePS("/func {\n");
        for (i = 0; i < shading->getNFuncs(); ++i) {
            if (i < shading->getNFuncs() - 1) {
                writePS("dup\n");
            }
            cvtFunction(shading->getFunc(i));
            writePS("exec\n");
            if (i < shading->getNFuncs() - 1) {
                writePS("exch\n");
            }
        }
        writePS("} def\n");
    }
    writePSFmt("{0:.4g} {1:.4g} 0 radialSH\n", sMin, sMax);
    
    // extend the 'enclosed' case
    if (enclosed) {
        // extend the smaller circle
        if ((shading->getExtend0() && r0 <= r1) ||
            (shading->getExtend1() && r1 < r0)) {
            if (r0 <= r1) {
                ta = t0;
                ra = r0;
                xa = x0;
                ya = y0;
            } else {
                ta = t1;
                ra = r1;
                xa = x1;
                ya = y1;
            }
            if (level == psLevel2Sep || level == psLevel3Sep) {
                writePSFmt("{0:.4g} radialCol aload pop k\n", ta);
            } else {
                writePSFmt("{0:.4g} radialCol sc\n", ta);
            }
            writePSFmt("{0:.4g} {1:.4g} {2:.4g} 0 360 arc h f*\n", xa, ya, ra);
        }
        
        // extend the larger circle
        if ((shading->getExtend0() && r0 > r1) ||
            (shading->getExtend1() && r1 >= r0)) {
            if (r0 > r1) {
                ta = t0;
                ra = r0;
                xa = x0;
                ya = y0;
            } else {
                ta = t1;
                ra = r1;
                xa = x1;
                ya = y1;
            }
            if (level == psLevel2Sep || level == psLevel3Sep) {
                writePSFmt("{0:.4g} radialCol aload pop k\n", ta);
            } else {
                writePSFmt("{0:.4g} radialCol sc\n", ta);
            }
            writePSFmt("{0:.4g} {1:.4g} {2:.4g} 0 360 arc h\n", xa, ya, ra);
            writePSFmt("{0:.4g} {1:.4g} m {2:.4g} {3:.4g} l {4:.4g} {5:.4g} l {6:.4g} {7:.4g} l h f*\n",
                       xMin, yMin, xMin, yMax, xMax, yMax, xMax, yMin);
        }
    }
    
    return gTrue;
}

void PSOutputDev::clip(GfxState *state) {
    doPath(state->getPath());
    writePS("W\n");
}

void PSOutputDev::eoClip(GfxState *state) {
    doPath(state->getPath());
    writePS("W*\n");
}

void PSOutputDev::clipToStrokePath(GfxState *state) {
    doPath(state->getPath());
    writePS("Ws\n");
}

void PSOutputDev::doPath(GfxPath *path) {
    GfxSubpath *subpath;
    double x0, y0, x1, y1, x2, y2, x3, y3, x4, y4;
    int n, m, i, j;
    
    n = path->getNumSubpaths();
    
    if (n == 1 && path->getSubpath(0)->getNumPoints() == 5) {
        subpath = path->getSubpath(0);
        x0 = subpath->getX(0);
        y0 = subpath->getY(0);
        x4 = subpath->getX(4);
        y4 = subpath->getY(4);
        if (x4 == x0 && y4 == y0) {
            x1 = subpath->getX(1);
            y1 = subpath->getY(1);
            x2 = subpath->getX(2);
            y2 = subpath->getY(2);
            x3 = subpath->getX(3);
            y3 = subpath->getY(3);
            if (x0 == x1 && x2 == x3 && y0 == y3 && y1 == y2) {
                writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} re\n",
                           x0 < x2 ? x0 : x2, y0 < y1 ? y0 : y1,
                           fabs(x2 - x0), fabs(y1 - y0));
                return;
            } else if (x0 == x3 && x1 == x2 && y0 == y1 && y2 == y3) {
                writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} re\n",
                           x0 < x1 ? x0 : x1, y0 < y2 ? y0 : y2,
                           fabs(x1 - x0), fabs(y2 - y0));
                return;
            }
        }
    }
    
    for (i = 0; i < n; ++i) {
        subpath = path->getSubpath(i);
        m = subpath->getNumPoints();
        writePSFmt("{0:.4g} {1:.4g} m\n", subpath->getX(0), subpath->getY(0));
        j = 1;
        while (j < m) {
            if (subpath->getCurve(j)) {
                writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g} c\n",
                           subpath->getX(j), subpath->getY(j),
                           subpath->getX(j+1), subpath->getY(j+1),
                           subpath->getX(j+2), subpath->getY(j+2));
                j += 3;
            } else {
                writePSFmt("{0:.4g} {1:.4g} l\n", subpath->getX(j), subpath->getY(j));
                ++j;
            }
        }
        if (subpath->isClosed()) {
            writePS("h\n");
        }
    }
}

void PSOutputDev::drawString(GfxState *state, GString *s) {
    GfxFont *font;
    int wMode;
    Gushort *codeToGID;
    GString *s2;
    double dx, dy, dx2, dy2, originX, originY;
    char *p;
    UnicodeMap *uMap;
    CharCode code;
    Unicode u[8];
    char buf[8];
    int len, nChars, uLen, n, m, i, j;
    
    // check for invisible text -- this is used by Acrobat Capture
    if (state->getRender() == 3) {
        return;
    }
    
    // ignore empty strings
    if (s->getLength() == 0) {
        return;
    }
    
    // get the font
    if (!(font = state->getFont())) {
        return;
    }
    wMode = font->getWMode();
    
    // check for a subtitute 16-bit font
    uMap = NULL;
    codeToGID = NULL;
    if (font->isCIDFont()) {
        for (i = 0; i < font16EncLen; ++i) {
            if (font->getID()->num == font16Enc[i].fontID.num &&
                font->getID()->gen == font16Enc[i].fontID.gen) {
                uMap = globalParams->getUnicodeMap(font16Enc[i].enc);
                break;
            }
        }
        
        // check for a code-to-GID map
    } else {
        for (i = 0; i < font8InfoLen; ++i) {
            if (font->getID()->num == font8Info[i].fontID.num &&
                font->getID()->gen == font8Info[i].fontID.gen) {
                codeToGID = font8Info[i].codeToGID;
                break;
            }
        }
    }
    
    // compute width of chars in string, ignoring char spacing and word
    // spacing -- the Tj operator will adjust for the metrics of the
    // font that's actually used
    dx = dy = 0;
    nChars = 0;
    p = s->getCString();
    len = s->getLength();
    s2 = new GString();
    while (len > 0) {
        n = font->getNextChar(p, len, &code,
                              u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
                              &dx2, &dy2, &originX, &originY);
        if (font->isCIDFont()) {
            if (uMap) {
                for (i = 0; i < uLen; ++i) {
                    m = uMap->mapUnicode(u[i], buf, (int)sizeof(buf));
                    for (j = 0; j < m; ++j) {
                        s2->append(buf[j]);
                    }
                }
                //~ this really needs to get the number of chars in the target
                //~ encoding - which may be more than the number of Unicode
                //~ chars
                nChars += uLen;
            } else {
                s2->append((char)((code >> 8) & 0xff));
                s2->append((char)(code & 0xff));
                ++nChars;
            }
        } else {
            if (!codeToGID || codeToGID[code]) {
                s2->append((char)code);
            }
        }
        dx += dx2;
        dy += dy2;
        p += n;
        len -= n;
    }
    dx *= state->getFontSize() * state->getHorizScaling();
    dy *= state->getFontSize();
    if (uMap) {
        uMap->decRefCnt();
    }
    
    if (s2->getLength() > 0) {
        writePSString(s2);
        if (font->isCIDFont()) {
            if (wMode) {
                writePSFmt(" {0:d} {1:.4g} Tj16V\n", nChars, dy);
            } else {
                writePSFmt(" {0:d} {1:.4g} Tj16\n", nChars, dx);
            }
        } else {
            writePSFmt(" {0:.4g} Tj\n", dx);
        }
    }
    delete s2;
    
    if (state->getRender() & 4) {
        haveTextClip = gTrue;
    }
}

void PSOutputDev::endTextObject(GfxState *state) {
    if (haveTextClip) {
        writePS("Tclip\n");
        haveTextClip = gFalse;
    }
}

void PSOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
                                int width, int height, GBool invert,
                                GBool inlineImg) {
    int len;
    
    len = height * ((width + 7) / 8);
    switch (level) {
        case psLevel1:
        case psLevel1Sep:
            doImageL1(ref, NULL, invert, inlineImg, str, width, height, len);
            break;
        case psLevel2:
        case psLevel2Sep:
            doImageL2(ref, NULL, invert, inlineImg, str, width, height, len,
                      NULL, NULL, 0, 0, gFalse);
            break;
        case psLevel3:
        case psLevel3Sep:
            doImageL3(ref, NULL, invert, inlineImg, str, width, height, len,
                      NULL, NULL, 0, 0, gFalse);
            break;
    }
}

void PSOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
                            int width, int height, GfxImageColorMap *colorMap,
                            int *maskColors, GBool inlineImg) {
    int len;
    
    len = height * ((width * colorMap->getNumPixelComps() *
                     colorMap->getBits() + 7) / 8);
    switch (level) {
        case psLevel1:
            doImageL1(ref, colorMap, gFalse, inlineImg, str, width, height, len);
            break;
        case psLevel1Sep:
            //~ handle indexed, separation, ... color spaces
            doImageL1Sep(colorMap, gFalse, inlineImg, str, width, height, len);
            break;
        case psLevel2:
        case psLevel2Sep:
            doImageL2(ref, colorMap, gFalse, inlineImg, str,
                      width, height, len, maskColors, NULL, 0, 0, gFalse);
            break;
        case psLevel3:
        case psLevel3Sep:
            doImageL3(ref, colorMap, gFalse, inlineImg, str,
                      width, height, len, maskColors, NULL, 0, 0, gFalse);
            break;
    }
    t3Cacheable = gFalse;
}

void PSOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str,
                                  int width, int height,
                                  GfxImageColorMap *colorMap,
                                  Stream *maskStr,
                                  int maskWidth, int maskHeight,
                                  GBool maskInvert) {
    int len;
    
    len = height * ((width * colorMap->getNumPixelComps() *
                     colorMap->getBits() + 7) / 8);
    switch (level) {
        case psLevel1:
            doImageL1(ref, colorMap, gFalse, gFalse, str, width, height, len);
            break;
        case psLevel1Sep:
            //~ handle indexed, separation, ... color spaces
            doImageL1Sep(colorMap, gFalse, gFalse, str, width, height, len);
            break;
        case psLevel2:
        case psLevel2Sep:
            doImageL2(ref, colorMap, gFalse, gFalse, str, width, height, len,
                      NULL, maskStr, maskWidth, maskHeight, maskInvert);
            break;
        case psLevel3:
        case psLevel3Sep:
            doImageL3(ref, colorMap, gFalse, gFalse, str, width, height, len,
                      NULL, maskStr, maskWidth, maskHeight, maskInvert);
            break;
    }
    t3Cacheable = gFalse;
}

void PSOutputDev::doImageL1(Object *ref, GfxImageColorMap *colorMap,
                            GBool invert, GBool inlineImg,
                            Stream *str, int width, int height, int len) {
    ImageStream *imgStr;
    Guchar pixBuf[gfxColorMaxComps];
    GfxGray gray;
    int col, x, y, c, i;
    
    if ((inType3Char || preload) && !colorMap) {
        if (inlineImg) {
            // create an array
            str = new FixedLengthEncoder(str, len);
            str = new ASCIIHexEncoder(str);
            str->reset();
            col = 0;
            writePS("[<");
            do {
                do {
                    c = str->getChar();
                } while (c == '\n' || c == '\r');
                if (c == '>' || c == EOF) {
                    break;
                }
                writePSChar(c);
                ++col;
                // each line is: "<...data...><eol>"
                // so max data length = 255 - 4 = 251
                // but make it 240 just to be safe
                // chunks are 2 bytes each, so we need to stop on an even col number
                if (col == 240) {
                    writePS(">\n<");
                    col = 0;
                }
            } while (c != '>' && c != EOF);
            writePS(">]\n");
            writePS("0\n");
            str->close();
            delete str;
        } else {
            // set up to use the array already created by setupImages()
            writePSFmt("ImData_{0:d}_{1:d} 0\n", ref->getRefNum(), ref->getRefGen());
        }
    }
    
    // image/imagemask command
    if ((inType3Char || preload) && !colorMap) {
        writePSFmt("{0:d} {1:d} {2:s} [{3:d} 0 0 {4:d} 0 {5:d}] pdfImM1a\n",
                   width, height, invert ? "true" : "false",
                   width, -height, height);
    } else if (colorMap) {
        writePSFmt("{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1\n",
                   width, height,
                   width, -height, height);
    } else {
        writePSFmt("{0:d} {1:d} {2:s} [{3:d} 0 0 {4:d} 0 {5:d}] pdfImM1\n",
                   width, height, invert ? "true" : "false",
                   width, -height, height);
    }
    
    // image data
    if (!((inType3Char || preload) && !colorMap)) {
        
        if (colorMap) {
            
            // set up to process the data stream
            imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(),
                                     colorMap->getBits());
            imgStr->reset();
            
            // process the data stream
            i = 0;
            for (y = 0; y < height; ++y) {
                
                // write the line
                for (x = 0; x < width; ++x) {
                    imgStr->getPixel(pixBuf);
                    colorMap->getGray(pixBuf, &gray);
                    writePSFmt("{0:02x}", colToByte(gray));
                    if (++i == 32) {
                        writePSChar('\n');
                        i = 0;
                    }
                }
            }
            if (i != 0) {
                writePSChar('\n');
            }
            str->close();
            delete imgStr;
            
            // imagemask
        } else {
            str->reset();
            i = 0;
            for (y = 0; y < height; ++y) {
                for (x = 0; x < width; x += 8) {
                    writePSFmt("{0:02x}", str->getChar() & 0xff);
                    if (++i == 32) {
                        writePSChar('\n');
                        i = 0;
                    }
                }
            }
            if (i != 0) {
                writePSChar('\n');
            }
            str->close();
        }
    }
}

void PSOutputDev::doImageL1Sep(GfxImageColorMap *colorMap,
                               GBool invert, GBool inlineImg,
                               Stream *str, int width, int height, int len) {
    ImageStream *imgStr;
    Guchar *lineBuf;
    Guchar pixBuf[gfxColorMaxComps];
    GfxCMYK cmyk;
    int x, y, i, comp;
    
    // width, height, matrix, bits per component
    writePSFmt("{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1Sep\n",
               width, height,
               width, -height, height);
    
    // allocate a line buffer
    lineBuf = (Guchar *)gmalloc(4 * width);
    
    // set up to process the data stream
    imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(),
                             colorMap->getBits());
    imgStr->reset();
    
    // process the data stream
    i = 0;
    for (y = 0; y < height; ++y) {
        
        // read the line
        for (x = 0; x < width; ++x) {
            imgStr->getPixel(pixBuf);
            colorMap->getCMYK(pixBuf, &cmyk);
            lineBuf[4*x+0] = colToByte(cmyk.c);
            lineBuf[4*x+1] = colToByte(cmyk.m);
            lineBuf[4*x+2] = colToByte(cmyk.y);
            lineBuf[4*x+3] = colToByte(cmyk.k);
            addProcessColor(colToDbl(cmyk.c), colToDbl(cmyk.m),
                            colToDbl(cmyk.y), colToDbl(cmyk.k));
        }
        
        // write one line of each color component
        for (comp = 0; comp < 4; ++comp) {
            for (x = 0; x < width; ++x) {
                writePSFmt("{0:02x}", lineBuf[4*x + comp]);
                if (++i == 32) {
                    writePSChar('\n');
                    i = 0;
                }
            }
        }
    }
    
    if (i != 0) {
        writePSChar('\n');
    }
    
    str->close();
    delete imgStr;
    gfree(lineBuf);
}

void PSOutputDev::doImageL2(Object *ref, GfxImageColorMap *colorMap,
                            GBool invert, GBool inlineImg,
                            Stream *str, int width, int height, int len,
                            int *maskColors, Stream *maskStr,
                            int maskWidth, int maskHeight, GBool maskInvert) {
    Stream *str2;
    ImageStream *imgStr;
    Guchar *line;
    PSOutImgClipRect *rects0, *rects1, *rectsTmp, *rectsOut;
    int rects0Len, rects1Len, rectsSize, rectsOutLen, rectsOutSize;
    GBool emitRect, addRect, extendRect;
    GString *s;
    int n, numComps;
    GBool useRLE, useASCII, useASCIIHex, useCompressed;
    GfxSeparationColorSpace *sepCS;
    GfxColor color;
    GfxCMYK cmyk;
    int c;
    int col, i, j, x0, x1, y, maskXor;
    
    // color key masking
    if (maskColors && colorMap && !inlineImg) {
        // can't read the stream twice for inline images -- but masking
        // isn't allowed with inline images anyway
        numComps = colorMap->getNumPixelComps();
        imgStr = new ImageStream(str, width, numComps, colorMap->getBits());
        imgStr->reset();
        rects0Len = rects1Len = rectsOutLen = 0;
        rectsSize = rectsOutSize = 64;
        rects0 = (PSOutImgClipRect *)gmallocn(rectsSize, sizeof(PSOutImgClipRect));
        rects1 = (PSOutImgClipRect *)gmallocn(rectsSize, sizeof(PSOutImgClipRect));
        rectsOut = (PSOutImgClipRect *)gmallocn(rectsOutSize,
                                                sizeof(PSOutImgClipRect));
        for (y = 0; y < height; ++y) {
            if (!(line = imgStr->getLine())) {
                break;
            }
            i = 0;
            rects1Len = 0;
            for (x0 = 0; x0 < width; ++x0) {
                for (j = 0; j < numComps; ++j) {
                    if (line[x0*numComps+j] < maskColors[2*j] ||
                        line[x0*numComps+j] > maskColors[2*j+1]) {
                        break;
                    }
                }
                if (j < numComps) {
                    break;
                }
            }
            for (x1 = x0; x1 < width; ++x1) {
                for (j = 0; j < numComps; ++j) {
                    if (line[x1*numComps+j] < maskColors[2*j] ||
                        line[x1*numComps+j] > maskColors[2*j+1]) {
                        break;
                    }
                }
                if (j == numComps) {
                    break;
                }
            }
            while (x0 < width || i < rects0Len) {
                emitRect = addRect = extendRect = gFalse;
                if (x0 >= width) {
                    emitRect = gTrue;
                } else if (i >= rects0Len) {
                    addRect = gTrue;
                } else if (rects0[i].x0 < x0) {
                    emitRect = gTrue;
                } else if (x0 < rects0[i].x0) {
                    addRect = gTrue;
                } else if (rects0[i].x1 == x1) {
                    extendRect = gTrue;
                } else {
                    emitRect = addRect = gTrue;
                }
                if (emitRect) {
                    if (rectsOutLen == rectsOutSize) {
                        rectsOutSize *= 2;
                        rectsOut = (PSOutImgClipRect *)greallocn(rectsOut, rectsOutSize,
                                                                 sizeof(PSOutImgClipRect));
                    }
                    rectsOut[rectsOutLen].x0 = rects0[i].x0;
                    rectsOut[rectsOutLen].x1 = rects0[i].x1;
                    rectsOut[rectsOutLen].y0 = height - y - 1;
                    rectsOut[rectsOutLen].y1 = height - rects0[i].y0 - 1;
                    ++rectsOutLen;
                    ++i;
                }
                if (addRect || extendRect) {
                    if (rects1Len == rectsSize) {
                        rectsSize *= 2;
                        rects0 = (PSOutImgClipRect *)greallocn(rects0, rectsSize,
                                                               sizeof(PSOutImgClipRect));
                        rects1 = (PSOutImgClipRect *)greallocn(rects1, rectsSize,
                                                               sizeof(PSOutImgClipRect));
                    }
                    rects1[rects1Len].x0 = x0;
                    rects1[rects1Len].x1 = x1;
                    if (addRect) {
                        rects1[rects1Len].y0 = y;
                    }
                    if (extendRect) {
                        rects1[rects1Len].y0 = rects0[i].y0;
                        ++i;
                    }
                    ++rects1Len;
                    for (x0 = x1; x0 < width; ++x0) {
                        for (j = 0; j < numComps; ++j) {
                            if (line[x0*numComps+j] < maskColors[2*j] ||
                                line[x0*numComps+j] > maskColors[2*j+1]) {
                                break;
                            }
                        }
                        if (j < numComps) {
                            break;
                        }
                    }
                    for (x1 = x0; x1 < width; ++x1) {
                        for (j = 0; j < numComps; ++j) {
                            if (line[x1*numComps+j] < maskColors[2*j] ||
                                line[x1*numComps+j] > maskColors[2*j+1]) {
                                break;
                            }
                        }
                        if (j == numComps) {
                            break;
                        }
                    }
                }
            }
            rectsTmp = rects0;
            rects0 = rects1;
            rects1 = rectsTmp;
            i = rects0Len;
            rects0Len = rects1Len;
            rects1Len = i;
        }
        for (i = 0; i < rects0Len; ++i) {
            if (rectsOutLen == rectsOutSize) {
                rectsOutSize *= 2;
                rectsOut = (PSOutImgClipRect *)greallocn(rectsOut, rectsOutSize,
                                                         sizeof(PSOutImgClipRect));
            }
            rectsOut[rectsOutLen].x0 = rects0[i].x0;
            rectsOut[rectsOutLen].x1 = rects0[i].x1;
            rectsOut[rectsOutLen].y0 = height - y - 1;
            rectsOut[rectsOutLen].y1 = height - rects0[i].y0 - 1;
            ++rectsOutLen;
        }
        writePSFmt("{0:d} array 0\n", rectsOutLen * 4);
        for (i = 0; i < rectsOutLen; ++i) {
            writePSFmt("[{0:d} {1:d} {2:d} {3:d}] pr\n",
                       rectsOut[i].x0, rectsOut[i].y0,
                       rectsOut[i].x1 - rectsOut[i].x0,
                       rectsOut[i].y1 - rectsOut[i].y0);
        }
        writePSFmt("pop {0:d} {1:d} pdfImClip\n", width, height);
        gfree(rectsOut);
        gfree(rects0);
        gfree(rects1);
        delete imgStr;
        str->close();
        
        // explicit masking
    } else if (maskStr) {
        imgStr = new ImageStream(maskStr, maskWidth, 1, 1);
        imgStr->reset();
        rects0Len = rects1Len = rectsOutLen = 0;
        rectsSize = rectsOutSize = 64;
        rects0 = (PSOutImgClipRect *)gmallocn(rectsSize, sizeof(PSOutImgClipRect));
        rects1 = (PSOutImgClipRect *)gmallocn(rectsSize, sizeof(PSOutImgClipRect));
        rectsOut = (PSOutImgClipRect *)gmallocn(rectsOutSize,
                                                sizeof(PSOutImgClipRect));
        maskXor = maskInvert ? 1 : 0;
        for (y = 0; y < maskHeight; ++y) {
            if (!(line = imgStr->getLine())) {
                break;
            }
            i = 0;
            rects1Len = 0;
            for (x0 = 0; x0 < maskWidth && (line[x0] ^ maskXor); ++x0) ;
            for (x1 = x0; x1 < maskWidth && !(line[x1] ^ maskXor); ++x1) ;
            while (x0 < maskWidth || i < rects0Len) {
                emitRect = addRect = extendRect = gFalse;
                if (x0 >= maskWidth) {
                    emitRect = gTrue;
                } else if (i >= rects0Len) {
                    addRect = gTrue;
                } else if (rects0[i].x0 < x0) {
                    emitRect = gTrue;
                } else if (x0 < rects0[i].x0) {
                    addRect = gTrue;
                } else if (rects0[i].x1 == x1) {
                    extendRect = gTrue;
                } else {
                    emitRect = addRect = gTrue;
                }
                if (emitRect) {
                    if (rectsOutLen == rectsOutSize) {
                        rectsOutSize *= 2;
                        rectsOut = (PSOutImgClipRect *)greallocn(rectsOut, rectsOutSize,
                                                                 sizeof(PSOutImgClipRect));
                    }
                    rectsOut[rectsOutLen].x0 = rects0[i].x0;
                    rectsOut[rectsOutLen].x1 = rects0[i].x1;
                    rectsOut[rectsOutLen].y0 = maskHeight - y - 1;
                    rectsOut[rectsOutLen].y1 = maskHeight - rects0[i].y0 - 1;
                    ++rectsOutLen;
                    ++i;
                }
                if (addRect || extendRect) {
                    if (rects1Len == rectsSize) {
                        rectsSize *= 2;
                        rects0 = (PSOutImgClipRect *)greallocn(rects0, rectsSize,
                                                               sizeof(PSOutImgClipRect));
                        rects1 = (PSOutImgClipRect *)greallocn(rects1, rectsSize,
                                                               sizeof(PSOutImgClipRect));
                    }
                    rects1[rects1Len].x0 = x0;
                    rects1[rects1Len].x1 = x1;
                    if (addRect) {
                        rects1[rects1Len].y0 = y;
                    }
                    if (extendRect) {
                        rects1[rects1Len].y0 = rects0[i].y0;
                        ++i;
                    }
                    ++rects1Len;
                    for (x0 = x1; x0 < maskWidth && (line[x0] ^ maskXor); ++x0) ;
                    for (x1 = x0; x1 < maskWidth && !(line[x1] ^ maskXor); ++x1) ;
                }
            }
            rectsTmp = rects0;
            rects0 = rects1;
            rects1 = rectsTmp;
            i = rects0Len;
            rects0Len = rects1Len;
            rects1Len = i;
        }
        for (i = 0; i < rects0Len; ++i) {
            if (rectsOutLen == rectsOutSize) {
                rectsOutSize *= 2;
                rectsOut = (PSOutImgClipRect *)greallocn(rectsOut, rectsOutSize,
                                                         sizeof(PSOutImgClipRect));
            }
            rectsOut[rectsOutLen].x0 = rects0[i].x0;
            rectsOut[rectsOutLen].x1 = rects0[i].x1;
            rectsOut[rectsOutLen].y0 = maskHeight - y - 1;
            rectsOut[rectsOutLen].y1 = maskHeight - rects0[i].y0 - 1;
            ++rectsOutLen;
        }
        writePSFmt("{0:d} array 0\n", rectsOutLen * 4);
        for (i = 0; i < rectsOutLen; ++i) {
            writePSFmt("[{0:d} {1:d} {2:d} {3:d}] pr\n",
                       rectsOut[i].x0, rectsOut[i].y0,
                       rectsOut[i].x1 - rectsOut[i].x0,
                       rectsOut[i].y1 - rectsOut[i].y0);
        }
        writePSFmt("pop {0:d} {1:d} pdfImClip\n", maskWidth, maskHeight);
        gfree(rectsOut);
        gfree(rects0);
        gfree(rects1);
        delete imgStr;
        maskStr->close();
    }
    
    // color space
    if (colorMap) {
        dumpColorSpaceL2(colorMap->getColorSpace(), gFalse, gTrue, gFalse);
        writePS(" setcolorspace\n");
    }
    
    useASCIIHex = globalParams->getPSASCIIHex();
    
    // set up the image data
    if (mode == psModeForm || inType3Char || preload) {
        if (inlineImg) {
            // create an array
            str2 = new FixedLengthEncoder(str, len);
            str2 = new RunLengthEncoder(str2);
            if (useASCIIHex) {
                str2 = new ASCIIHexEncoder(str2);
            } else {
                str2 = new ASCII85Encoder(str2);
            }
            str2->reset();
            col = 0;
            writePS((char *)(useASCIIHex ? "[<" : "[<~"));
            do {
                do {
                    c = str2->getChar();
                } while (c == '\n' || c == '\r');
                if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
                    break;
                }
                if (c == 'z') {
                    writePSChar(c);
                    ++col;
                } else {
                    writePSChar(c);
                    ++col;
                    for (i = 1; i <= (useASCIIHex ? 1 : 4); ++i) {
                        do {
                            c = str2->getChar();
                        } while (c == '\n' || c == '\r');
                        if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
                            break;
                        }
                        writePSChar(c);
                        ++col;
                    }
                }
                // each line is: "<~...data...~><eol>"
                // so max data length = 255 - 6 = 249
                // chunks are 1 or 5 bytes each, so we have to stop at 245
                // but make it 240 just to be safe
                if (col > 240) {
                    writePS((char *)(useASCIIHex ? ">\n<" : "~>\n<~"));
                    col = 0;
                }
            } while (c != (useASCIIHex ? '>' : '~') && c != EOF);
            writePS((char *)(useASCIIHex ? ">\n" : "~>\n"));
            // add an extra entry because the RunLengthDecode filter may
            // read past the end
            writePS("<>]\n");
            writePS("0\n");
            str2->close();
            delete str2;
        } else {
            // set up to use the array already created by setupImages()
            writePSFmt("ImData_{0:d}_{1:d} 0\n", ref->getRefNum(), ref->getRefGen());
        }
    }
    
    // image dictionary
    writePS("<<\n  /ImageType 1\n");
    
    // width, height, matrix, bits per component
    writePSFmt("  /Width {0:d}\n", width);
    writePSFmt("  /Height {0:d}\n", height);
    writePSFmt("  /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n",
               width, -height, height);
    if (colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) {
        writePS("  /BitsPerComponent 8\n");
    } else {
        writePSFmt("  /BitsPerComponent {0:d}\n",
                   colorMap ? colorMap->getBits() : 1);
    }
    
    // decode 
    if (colorMap) {
        writePS("  /Decode [");
        if ((level == psLevel2Sep || level == psLevel3Sep) &&
            colorMap->getColorSpace()->getMode() == csSeparation) {
            // this matches up with the code in the pdfImSep operator
            n = (1 << colorMap->getBits()) - 1;
            writePSFmt("{0:.4g} {1:.4g}", colorMap->getDecodeLow(0) * n,
                       colorMap->getDecodeHigh(0) * n);
        } else if (colorMap->getColorSpace()->getMode() == csDeviceN) {
            numComps = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->
            getAlt()->getNComps();
            for (i = 0; i < numComps; ++i) {
                if (i > 0) {
                    writePS(" ");
                }
                writePS("0 1");
            }
        } else {
            numComps = colorMap->getNumPixelComps();
            for (i = 0; i < numComps; ++i) {
                if (i > 0) {
                    writePS(" ");
                }
                writePSFmt("{0:.4g} {1:.4g}",
                           colorMap->getDecodeLow(i), colorMap->getDecodeHigh(i));
            }
        }
        writePS("]\n");
    } else {
        writePSFmt("  /Decode [{0:d} {1:d}]\n", invert ? 1 : 0, invert ? 0 : 1);
    }
    
    // data source
    if (mode == psModeForm || inType3Char || preload) {
        writePS("  /DataSource { 2 copy get exch 1 add exch }\n");
    } else {
        writePS("  /DataSource currentfile\n");
    }
    
    // filters
    s = str->getPSFilter(level < psLevel2 ? 1 : level < psLevel3 ? 2 : 3,
                         "    ");
    if ((colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) ||
        inlineImg || !s) {
        useRLE = gTrue;
        useASCII = !(mode == psModeForm || inType3Char || preload);
        useCompressed = gFalse;
    } else {
        useRLE = gFalse;
        useASCII = str->isBinary() &&
        !(mode == psModeForm || inType3Char || preload);
        useCompressed = gTrue;
    }
    if (useASCII) {
        writePSFmt("    /ASCII{0:s}Decode filter\n",
                   useASCIIHex ? "Hex" : "85");
    }
    if (useRLE) {
        writePS("    /RunLengthDecode filter\n");
    }
    if (useCompressed) {
        writePS(s->getCString());
    }
    if (s) {
        delete s;
    }
    
    if (mode == psModeForm || inType3Char || preload) {
        
        // end of image dictionary
        writePSFmt(">>\n{0:s}\n", colorMap ? "image" : "imagemask");
        
        // get rid of the array and index
        writePS("pop pop\n");
        
    } else {
        
        // cut off inline image streams at appropriate length
        if (inlineImg) {
            str = new FixedLengthEncoder(str, len);
        } else if (useCompressed) {
            str = str->getUndecodedStream();
        }
        
        // recode DeviceN data
        if (colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) {
            str = new DeviceNRecoder(str, width, height, colorMap);
        }
        
        // add RunLengthEncode and ASCIIHex/85 encode filters
        if (useRLE) {
            str = new RunLengthEncoder(str);
        }
        if (useASCII) {
            if (useASCIIHex) {
                str = new ASCIIHexEncoder(str);
            } else {
                str = new ASCII85Encoder(str);
            }
        }
        
        // end of image dictionary
        writePS(">>\n");
#if OPI_SUPPORT
        if (opi13Nest) {
            if (inlineImg) {
                // this can't happen -- OPI dictionaries are in XObjects
                error(-1, "Internal: OPI in inline image");
                n = 0;
            } else {
                // need to read the stream to count characters -- the length
                // is data-dependent (because of ASCII and RLE filters)
                str->reset();
                n = 0;
                while ((c = str->getChar()) != EOF) {
                    ++n;
                }
                str->close();
            }
            // +6/7 for "pdfIm\n" / "pdfImM\n"
            // +8 for newline + trailer
            n += colorMap ? 14 : 15;
            writePSFmt("%%BeginData: {0:d} Hex Bytes\n", n);
        }
#endif
        if ((level == psLevel2Sep || level == psLevel3Sep) && colorMap &&
            colorMap->getColorSpace()->getMode() == csSeparation) {
            color.c[0] = gfxColorComp1;
            sepCS = (GfxSeparationColorSpace *)colorMap->getColorSpace();
            sepCS->getCMYK(&color, &cmyk);
            writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} ({4:t}) pdfImSep\n",
                       colToDbl(cmyk.c), colToDbl(cmyk.m),
                       colToDbl(cmyk.y), colToDbl(cmyk.k),
                       sepCS->getName());
        } else {
            writePSFmt("{0:s}\n", colorMap ? "pdfIm" : "pdfImM");
        }
        
        // copy the stream data
        str->reset();
        while ((c = str->getChar()) != EOF) {
            writePSChar(c);
        }
        str->close();
        
        // add newline and trailer to the end
        writePSChar('\n');
        writePS("%-EOD-\n");
#if OPI_SUPPORT
        if (opi13Nest) {
            writePS("%%EndData\n");
        }
#endif
        
        // delete encoders
        if (useRLE || useASCII || inlineImg) {
            delete str;
        }
    }
    
    if ((maskColors && colorMap && !inlineImg) || maskStr) {
        writePS("pdfImClipEnd\n");
    }
}

//~ this doesn't currently support OPI
//~ this doesn't currently support OPI
void PSOutputDev::doImageL3(Object *ref, GfxImageColorMap *colorMap,
                            GBool invert, GBool inlineImg,
                            Stream *str, int width, int height, int len,
                            int *maskColors, Stream *maskStr,
                            int maskWidth, int maskHeight, GBool maskInvert) {
    Stream *str2;
    GString *s;
    int n, numComps;
    GBool useRLE, useASCII, useASCIIHex, useCompressed;
    GBool maskUseRLE, maskUseASCII, maskUseCompressed;
    GfxSeparationColorSpace *sepCS;
    GfxColor color;
    GfxCMYK cmyk;
    int c;
    int col, i;
    
    useASCIIHex = globalParams->getPSASCIIHex();
    useRLE = useASCII = useCompressed = gFalse; // make gcc happy
    maskUseRLE = maskUseASCII = maskUseCompressed = gFalse; // make gcc happy
    
    // color space
    if (colorMap) {
        dumpColorSpaceL2(colorMap->getColorSpace(), gFalse, gTrue, gFalse);
        writePS(" setcolorspace\n");
    }
    
    // set up the image data
    if (mode == psModeForm || inType3Char || preload) {
        if (inlineImg) {
            // create an array
            str2 = new FixedLengthEncoder(str, len);
            str2 = new RunLengthEncoder(str2);
            if (useASCIIHex) {
                str2 = new ASCIIHexEncoder(str2);
            } else {
                str2 = new ASCII85Encoder(str2);
            }
            str2->reset();
            col = 0;
            writePS((char *)(useASCIIHex ? "[<" : "[<~"));
            do {
                do {
                    c = str2->getChar();
                } while (c == '\n' || c == '\r');
                if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
                    break;
                }
                if (c == 'z') {
                    writePSChar(c);
                    ++col;
                } else {
                    writePSChar(c);
                    ++col;
                    for (i = 1; i <= (useASCIIHex ? 1 : 4); ++i) {
                        do {
                            c = str2->getChar();
                        } while (c == '\n' || c == '\r');
                        if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
                            break;
                        }
                        writePSChar(c);
                        ++col;
                    }
                }
                // each line is: "<~...data...~><eol>"
                // so max data length = 255 - 6 = 249
                // chunks are 1 or 5 bytes each, so we have to stop at 245
                // but make it 240 just to be safe
                if (col > 240) {
                    writePS((char *)(useASCIIHex ? ">\n<" : "~>\n<~"));
                    col = 0;
                }
            } while (c != (useASCIIHex ? '>' : '~') && c != EOF);
            writePS((char *)(useASCIIHex ? ">\n" : "~>\n"));
            // add an extra entry because the RunLengthDecode filter may
            // read past the end
            writePS("<>]\n");
            writePS("0\n");
            str2->close();
            delete str2;
        } else {
            // set up to use the array already created by setupImages()
            writePSFmt("ImData_{0:d}_{1:d} 0\n", ref->getRefNum(), ref->getRefGen());
        }
    }
    
    // explicit masking
    if (maskStr) {
        writePS("<<\n  /ImageType 3\n");
        writePS("  /InterleaveType 3\n");
        writePS("  /DataDict\n");
    }
    
    // image (data) dictionary
    writePSFmt("<<\n  /ImageType {0:d}\n", (maskColors && colorMap) ? 4 : 1);
    
    // color key masking
    if (maskColors && colorMap) {
        writePS("  /MaskColor [\n");
        numComps = colorMap->getNumPixelComps();
        for (i = 0; i < 2 * numComps; i += 2) {
            writePSFmt("    {0:d} {1:d}\n", maskColors[i], maskColors[i+1]);
        }
        writePS("  ]\n");
    }
    
    // width, height, matrix, bits per component
    writePSFmt("  /Width {0:d}\n", width);
    writePSFmt("  /Height {0:d}\n", height);
    writePSFmt("  /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n",
               width, -height, height);
    if (colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) {
        writePS("  /BitsPerComponent 8\n");
    } else {
        writePSFmt("  /BitsPerComponent {0:d}\n",
                   colorMap ? colorMap->getBits() : 1);
    }
    
    // decode 
    if (colorMap) {
        writePS("  /Decode [");
        if ((level == psLevel2Sep || level == psLevel3Sep) &&
            colorMap->getColorSpace()->getMode() == csSeparation) {
            // this matches up with the code in the pdfImSep operator
            n = (1 << colorMap->getBits()) - 1;
            writePSFmt("{0:.4g} {1:.4g}", colorMap->getDecodeLow(0) * n,
                       colorMap->getDecodeHigh(0) * n);
        } else if (colorMap->getColorSpace()->getMode() == csDeviceN) {
            numComps = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->
            getAlt()->getNComps();
            for (i = 0; i < numComps; ++i) {
                if (i > 0) {
                    writePS(" ");
                }
                writePS("0 1");
            }
        } else {
            numComps = colorMap->getNumPixelComps();
            for (i = 0; i < numComps; ++i) {
                if (i > 0) {
                    writePS(" ");
                }
                writePSFmt("{0:.4g} {1:.4g}", colorMap->getDecodeLow(i),
                           colorMap->getDecodeHigh(i));
            }
        }
        writePS("]\n");
    } else {
        writePSFmt("  /Decode [{0:d} {1:d}]\n", invert ? 1 : 0, invert ? 0 : 1);
    }
    
    // data source
    if (mode == psModeForm || inType3Char || preload) {
        writePS("  /DataSource { 2 copy get exch 1 add exch }\n");
    } else {
        writePS("  /DataSource currentfile\n");
    }
    
    // filters
    s = str->getPSFilter(level < psLevel2 ? 1 : level < psLevel3 ? 2 : 3,
                         "    ");
    if ((colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) ||
        inlineImg || !s) {
        useRLE = gTrue;
        useASCII = !(mode == psModeForm || inType3Char || preload);
        useCompressed = gFalse;
    } else {
        useRLE = gFalse;
        useASCII = str->isBinary() &&
        !(mode == psModeForm || inType3Char || preload);
        useCompressed = gTrue;
    }
    if (useASCII) {
        writePSFmt("    /ASCII{0:s}Decode filter\n",
                   useASCIIHex ? "Hex" : "85");
    }
    if (useRLE) {
        writePS("    /RunLengthDecode filter\n");
    }
    if (useCompressed) {
        writePS(s->getCString());
    }
    if (s) {
        delete s;
    }
    
    // end of image (data) dictionary
    writePS(">>\n");
    
    // explicit masking
    if (maskStr) {
        writePS("  /MaskDict\n");
        writePS("<<\n");
        writePS("  /ImageType 1\n");
        writePSFmt("  /Width {0:d}\n", maskWidth);
        writePSFmt("  /Height {0:d}\n", maskHeight);
        writePSFmt("  /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n",
                   maskWidth, -maskHeight, maskHeight);
        writePS("  /BitsPerComponent 1\n");
        writePSFmt("  /Decode [{0:d} {1:d}]\n",
                   maskInvert ? 1 : 0, maskInvert ? 0 : 1);
        
        // mask data source
        writePS("  /DataSource currentfile\n");
        s = maskStr->getPSFilter(3, "    ");
        if (!s) {
            maskUseRLE = gTrue;
            maskUseASCII = gTrue;
            maskUseCompressed = gFalse;
        } else {
            maskUseRLE = gFalse;
            maskUseASCII = maskStr->isBinary();
            maskUseCompressed = gTrue;
        }
        if (maskUseASCII) {
            writePSFmt("    /ASCII{0:s}Decode filter\n",
                       useASCIIHex ? "Hex" : "85");
        }
        if (maskUseRLE) {
            writePS("    /RunLengthDecode filter\n");
        }
        if (maskUseCompressed) {
            writePS(s->getCString());
        }
        if (s) {
            delete s;
        }
        
        writePS(">>\n");
        writePS(">>\n");
    }
    
    if (mode == psModeForm || inType3Char || preload) {
        
        // image command
        writePSFmt("{0:s}\n", colorMap ? "image" : "imagemask");
        
    } else {
        
        if ((level == psLevel2Sep || level == psLevel3Sep) && colorMap &&
            colorMap->getColorSpace()->getMode() == csSeparation) {
            color.c[0] = gfxColorComp1;
            sepCS = (GfxSeparationColorSpace *)colorMap->getColorSpace();
            sepCS->getCMYK(&color, &cmyk);
            writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} ({4:t}) pdfImSep\n",
                       colToDbl(cmyk.c), colToDbl(cmyk.m),
                       colToDbl(cmyk.y), colToDbl(cmyk.k),
                       sepCS->getName());
        } else {
            writePSFmt("{0:s}\n", colorMap ? "pdfIm" : "pdfImM");
        }
        
    }
    
    // explicit masking
    if (maskStr) {
        
        if (maskUseCompressed) {
            maskStr = maskStr->getUndecodedStream();
        }
        
        // add RunLengthEncode and ASCIIHex/85 encode filters
        if (maskUseRLE) {
            maskStr = new RunLengthEncoder(maskStr);
        }
        if (maskUseASCII) {
            if (useASCIIHex) {
                maskStr = new ASCIIHexEncoder(maskStr);
            } else {
                maskStr = new ASCII85Encoder(maskStr);
            }
        }
        
        // copy the stream data
        maskStr->reset();
        while ((c = maskStr->getChar()) != EOF) {
            writePSChar(c);
        }
        maskStr->close();
        writePSChar('\n');
        
        // delete encoders
        if (maskUseRLE || maskUseASCII) {
            delete maskStr;
        }
    }
    
    // get rid of the array and index
    if (mode == psModeForm || inType3Char || preload) {
        writePS("pop pop\n");
        
        // image data
    } else {
        
        // cut off inline image streams at appropriate length
        if (inlineImg) {
            str = new FixedLengthEncoder(str, len);
        } else if (useCompressed) {
            str = str->getUndecodedStream();
        }
        
        // recode DeviceN data
        if (colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) {
            str = new DeviceNRecoder(str, width, height, colorMap);
        }
        
        // add RunLengthEncode and ASCIIHex/85 encode filters
        if (useRLE) {
            str = new RunLengthEncoder(str);
        }
        if (useASCII) {
            if (useASCIIHex) {
                str = new ASCIIHexEncoder(str);
            } else {
                str = new ASCII85Encoder(str);
            }
        }
        
        // copy the stream data
        str->reset();
        while ((c = str->getChar()) != EOF) {
            writePSChar(c);
        }
        str->close();
        
        // add newline and trailer to the end
        writePSChar('\n');
        writePS("%-EOD-\n");
        
        // delete encoders
        if (useRLE || useASCII || inlineImg) {
            delete str;
        }
    }
}

void PSOutputDev::dumpColorSpaceL2(GfxColorSpace *colorSpace,
                                   GBool genXform, GBool updateColors,
                                   GBool map01) {
    GfxCalGrayColorSpace *calGrayCS;
    GfxCalRGBColorSpace *calRGBCS;
    GfxLabColorSpace *labCS;
    GfxIndexedColorSpace *indexedCS;
    GfxSeparationColorSpace *separationCS;
    GfxDeviceNColorSpace *deviceNCS;
    GfxColorSpace *baseCS;
    Guchar *lookup, *p;
    double x[gfxColorMaxComps], y[gfxColorMaxComps];
    double low[gfxColorMaxComps], range[gfxColorMaxComps];
    GfxColor color;
    GfxCMYK cmyk;
    Function *func;
    int n, numComps, numAltComps;
    int byte;
    int i, j, k;
    
    switch (colorSpace->getMode()) {
            
        case csDeviceGray:
            writePS("/DeviceGray");
            if (genXform) {
                writePS(" {}");
            }
            if (updateColors) {
                processColors |= psProcessBlack;
            }
            break;
            
        case csCalGray:
            calGrayCS = (GfxCalGrayColorSpace *)colorSpace;
            writePS("[/CIEBasedA <<\n");
            writePSFmt(" /DecodeA {{{0:.4g} exp}} bind\n", calGrayCS->getGamma());
            writePSFmt(" /MatrixA [{0:.4g} {1:.4g} {2:.4g}]\n",
                       calGrayCS->getWhiteX(), calGrayCS->getWhiteY(),
                       calGrayCS->getWhiteZ());
            writePSFmt(" /WhitePoint [{0:.4g} {1:.4g} {2:.4g}]\n",
                       calGrayCS->getWhiteX(), calGrayCS->getWhiteY(),
                       calGrayCS->getWhiteZ());
            writePSFmt(" /BlackPoint [{0:.4g} {1:.4g} {2:.4g}]\n",
                       calGrayCS->getBlackX(), calGrayCS->getBlackY(),
                       calGrayCS->getBlackZ());
            writePS(">>]");
            if (genXform) {
                writePS(" {}");
            }
            if (updateColors) {
                processColors |= psProcessBlack;
            }
            break;
            
        case csDeviceRGB:
            writePS("/DeviceRGB");
            if (genXform) {
                writePS(" {}");
            }
            if (updateColors) {
                processColors |= psProcessCMYK;
            }
            break;
            
        case csCalRGB:
            calRGBCS = (GfxCalRGBColorSpace *)colorSpace;
            writePS("[/CIEBasedABC <<\n");
            writePSFmt(" /DecodeABC [{{{0:.4g} exp}} bind {{{1:.4g} exp}} bind {{{2:.4g} exp}} bind]\n",
                       calRGBCS->getGammaR(), calRGBCS->getGammaG(),
                       calRGBCS->getGammaB());
            writePSFmt(" /MatrixABC [{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g} {6:.4g} {7:.4g} {8:.4g}]\n",
                       calRGBCS->getMatrix()[0], calRGBCS->getMatrix()[1],
                       calRGBCS->getMatrix()[2], calRGBCS->getMatrix()[3],
                       calRGBCS->getMatrix()[4], calRGBCS->getMatrix()[5],
                       calRGBCS->getMatrix()[6], calRGBCS->getMatrix()[7],
                       calRGBCS->getMatrix()[8]);
            writePSFmt(" /WhitePoint [{0:.4g} {1:.4g} {2:.4g}]\n",
                       calRGBCS->getWhiteX(), calRGBCS->getWhiteY(),
                       calRGBCS->getWhiteZ());
            writePSFmt(" /BlackPoint [{0:.4g} {1:.4g} {2:.4g}]\n",
                       calRGBCS->getBlackX(), calRGBCS->getBlackY(),
                       calRGBCS->getBlackZ());
            writePS(">>]");
            if (genXform) {
                writePS(" {}");
            }
            if (updateColors) {
                processColors |= psProcessCMYK;
            }
            break;
            
        case csDeviceCMYK:
            writePS("/DeviceCMYK");
            if (genXform) {
                writePS(" {}");
            }
            if (updateColors) {
                processColors |= psProcessCMYK;
            }
            break;
            
        case csLab:
            labCS = (GfxLabColorSpace *)colorSpace;
            writePS("[/CIEBasedABC <<\n");
            if (map01) {
                writePS(" /RangeABC [0 1 0 1 0 1]\n");
                writePSFmt(" /DecodeABC [{{100 mul 16 add 116 div}} bind {{{0:.4g} mul {1:.4g} add}} bind {{{2:.4g} mul {3:.4g} add}} bind]\n",
                           (labCS->getAMax() - labCS->getAMin()) / 500.0,
                           labCS->getAMin() / 500.0,
                           (labCS->getBMax() - labCS->getBMin()) / 200.0,
                           labCS->getBMin() / 200.0);
            } else {
                writePSFmt(" /RangeABC [0 100 {0:.4g} {1:.4g} {2:.4g} {3:.4g}]\n",
                           labCS->getAMin(), labCS->getAMax(),
                           labCS->getBMin(), labCS->getBMax());
                writePS(" /DecodeABC [{16 add 116 div} bind {500 div} bind {200 div} bind]\n");
            }
            writePS(" /MatrixABC [1 1 1 1 0 0 0 0 -1]\n");
            writePS(" /DecodeLMN\n");
            writePS("   [{dup 6 29 div ge {dup dup mul mul}\n");
            writePSFmt("     {{4 29 div sub 108 841 div mul }} ifelse {0:.4g} mul}} bind\n",
                       labCS->getWhiteX());
            writePS("    {dup 6 29 div ge {dup dup mul mul}\n");
            writePSFmt("     {{4 29 div sub 108 841 div mul }} ifelse {0:.4g} mul}} bind\n",
                       labCS->getWhiteY());
            writePS("    {dup 6 29 div ge {dup dup mul mul}\n");
            writePSFmt("     {{4 29 div sub 108 841 div mul }} ifelse {0:.4g} mul}} bind]\n",
                       labCS->getWhiteZ());
            writePSFmt(" /WhitePoint [{0:.4g} {1:.4g} {2:.4g}]\n",
                       labCS->getWhiteX(), labCS->getWhiteY(), labCS->getWhiteZ());
            writePSFmt(" /BlackPoint [{0:.4g} {1:.4g} {2:.4g}]\n",
                       labCS->getBlackX(), labCS->getBlackY(), labCS->getBlackZ());
            writePS(">>]");
            if (genXform) {
                writePS(" {}");
            }
            if (updateColors) {
                processColors |= psProcessCMYK;
            }
            break;
            
        case csICCBased:
            // there is no transform function to the alternate color space, so
            // we can use it directly
            dumpColorSpaceL2(((GfxICCBasedColorSpace *)colorSpace)->getAlt(),
                             genXform, updateColors, gFalse);
            break;
            
        case csIndexed:
            indexedCS = (GfxIndexedColorSpace *)colorSpace;
            baseCS = indexedCS->getBase();
            writePS("[/Indexed ");
            dumpColorSpaceL2(baseCS, gFalse, gFalse, gTrue);
            n = indexedCS->getIndexHigh();
            numComps = baseCS->getNComps();
            lookup = indexedCS->getLookup();
            writePSFmt(" {0:d} <\n", n);
            if (baseCS->getMode() == csDeviceN) {
                func = ((GfxDeviceNColorSpace *)baseCS)->getTintTransformFunc();
                baseCS->getDefaultRanges(low, range, indexedCS->getIndexHigh());
                if (((GfxDeviceNColorSpace *)baseCS)->getAlt()->getMode() == csLab) {
                    labCS = (GfxLabColorSpace *)((GfxDeviceNColorSpace *)baseCS)->getAlt();
                } else {
                    labCS = NULL;
                }
                numAltComps = ((GfxDeviceNColorSpace *)baseCS)->getAlt()->getNComps();
                p = lookup;
                for (i = 0; i <= n; i += 8) {
                    writePS("  ");
                    for (j = i; j < i+8 && j <= n; ++j) {
                        for (k = 0; k < numComps; ++k) {
                            x[k] = low[k] + (*p++ / 255.0) * range[k];
                        }
                        func->transform(x, y);
                        if (labCS) {
                            y[0] /= 100.0;
                            y[1] = (y[1] - labCS->getAMin()) /
                            (labCS->getAMax() - labCS->getAMin());
                            y[2] = (y[2] - labCS->getBMin()) /
                            (labCS->getBMax() - labCS->getBMin());
                        }
                        for (k = 0; k < numAltComps; ++k) {
                            byte = (int)(y[k] * 255 + 0.5);
                            if (byte < 0) {
                                byte = 0;
                            } else if (byte > 255) {
                                byte = 255;
                            }
                            writePSFmt("{0:02x}", byte);
                        }
                        if (updateColors) {
                            color.c[0] = dblToCol(j);
                            indexedCS->getCMYK(&color, &cmyk);
                            addProcessColor(colToDbl(cmyk.c), colToDbl(cmyk.m),
                                            colToDbl(cmyk.y), colToDbl(cmyk.k));
                        }
                    }
                    writePS("\n");
                }
            } else {
                for (i = 0; i <= n; i += 8) {
                    writePS("  ");
                    for (j = i; j < i+8 && j <= n; ++j) {
                        for (k = 0; k < numComps; ++k) {
                            writePSFmt("{0:02x}", lookup[j * numComps + k]);
                        }
                        if (updateColors) {
                            color.c[0] = dblToCol(j);
                            indexedCS->getCMYK(&color, &cmyk);
                            addProcessColor(colToDbl(cmyk.c), colToDbl(cmyk.m),
                                            colToDbl(cmyk.y), colToDbl(cmyk.k));
                        }
                    }
                    writePS("\n");
                }
            }
            writePS(">]");
            if (genXform) {
                writePS(" {}");
            }
            break;
            
        case csSeparation:
            separationCS = (GfxSeparationColorSpace *)colorSpace;
            writePS("[/Separation ");
            writePSString(separationCS->getName());
            writePS(" ");
            dumpColorSpaceL2(separationCS->getAlt(), gFalse, gFalse, gFalse);
            writePS("\n");
            cvtFunction(separationCS->getFunc());
            writePS("]");
            if (genXform) {
                writePS(" {}");
            }
            if (updateColors) {
                addCustomColor(separationCS);
            }
            break;
            
        case csDeviceN:
            // DeviceN color spaces are a Level 3 PostScript feature.
            deviceNCS = (GfxDeviceNColorSpace *)colorSpace;
            dumpColorSpaceL2(deviceNCS->getAlt(), gFalse, updateColors, map01);
            if (genXform) {
                writePS(" ");
                cvtFunction(deviceNCS->getTintTransformFunc());
            }
            break;
            
        case csPattern:
            //~ unimplemented
            break;
    }
}

#if OPI_SUPPORT
void PSOutputDev::opiBegin(GfxState *state, Dict *opiDict) {
    Object dict;
    
    if (globalParams->getPSOPI()) {
        opiDict->lookup("2.0", &dict);
        if (dict.isDict()) {
            opiBegin20(state, dict.getDict());
            dict.free();
        } else {
            dict.free();
            opiDict->lookup("1.3", &dict);
            if (dict.isDict()) {
                opiBegin13(state, dict.getDict());
            }
            dict.free();
        }
    }
}

void PSOutputDev::opiBegin20(GfxState *state, Dict *dict) {
    Object obj1, obj2, obj3, obj4;
    double width, height, left, right, top, bottom;
    int w, h;
    int i;
    
    writePS("%%BeginOPI: 2.0\n");
    writePS("%%Distilled\n");
    
    dict->lookup("F", &obj1);
    if (getFileSpec(&obj1, &obj2)) {
        writePSFmt("%%ImageFileName: {0:t}\n", obj2.getString());
        obj2.free();
    }
    obj1.free();
    
    dict->lookup("MainImage", &obj1);
    if (obj1.isString()) {
        writePSFmt("%%MainImage: {0:t}\n", obj1.getString());
    }
    obj1.free();
    
    //~ ignoring 'Tags' entry
    //~ need to use writePSString() and deal with >255-char lines
    
    dict->lookup("Size", &obj1);
    if (obj1.isArray() && obj1.arrayGetLength() == 2) {
        obj1.arrayGet(0, &obj2);
        width = obj2.getNum();
        obj2.free();
        obj1.arrayGet(1, &obj2);
        height = obj2.getNum();
        obj2.free();
        writePSFmt("%%ImageDimensions: {0:.4g} {1:.4g}\n", width, height);
    }
    obj1.free();
    
    dict->lookup("CropRect", &obj1);
    if (obj1.isArray() && obj1.arrayGetLength() == 4) {
        obj1.arrayGet(0, &obj2);
        left = obj2.getNum();
        obj2.free();
        obj1.arrayGet(1, &obj2);
        top = obj2.getNum();
        obj2.free();
        obj1.arrayGet(2, &obj2);
        right = obj2.getNum();
        obj2.free();
        obj1.arrayGet(3, &obj2);
        bottom = obj2.getNum();
        obj2.free();
        writePSFmt("%%ImageCropRect: {0:.4g} {1:.4g} {2:.4g} {3:.4g}\n",
                   left, top, right, bottom);
    }
    obj1.free();
    
    dict->lookup("Overprint", &obj1);
    if (obj1.isBool()) {
        writePSFmt("%%ImageOverprint: {0:s}\n", obj1.getBool() ? "true" : "false");
    }
    obj1.free();
    
    dict->lookup("Inks", &obj1);
    if (obj1.isName()) {
        writePSFmt("%%ImageInks: {0:s}\n", obj1.getName());
    } else if (obj1.isArray() && obj1.arrayGetLength() >= 1) {
        obj1.arrayGet(0, &obj2);
        if (obj2.isName()) {
            writePSFmt("%%ImageInks: {0:s} {1:d}",
                       obj2.getName(), (obj1.arrayGetLength() - 1) / 2);
            for (i = 1; i+1 < obj1.arrayGetLength(); i += 2) {
                obj1.arrayGet(i, &obj3);
                obj1.arrayGet(i+1, &obj4);
                if (obj3.isString() && obj4.isNum()) {
                    writePS(" ");
                    writePSString(obj3.getString());
                    writePSFmt(" {0:.4g}", obj4.getNum());
                }
                obj3.free();
                obj4.free();
            }
            writePS("\n");
        }
        obj2.free();
    }
    obj1.free();
    
    writePS("gsave\n");
    
    writePS("%%BeginIncludedImage\n");
    
    dict->lookup("IncludedImageDimensions", &obj1);
    if (obj1.isArray() && obj1.arrayGetLength() == 2) {
        obj1.arrayGet(0, &obj2);
        w = obj2.getInt();
        obj2.free();
        obj1.arrayGet(1, &obj2);
        h = obj2.getInt();
        obj2.free();
        writePSFmt("%%IncludedImageDimensions: {0:d} {1:d}\n", w, h);
    }
    obj1.free();
    
    dict->lookup("IncludedImageQuality", &obj1);
    if (obj1.isNum()) {
        writePSFmt("%%IncludedImageQuality: {0:.4g}\n", obj1.getNum());
    }
    obj1.free();
    
    ++opi20Nest;
}

void PSOutputDev::opiBegin13(GfxState *state, Dict *dict) {
    Object obj1, obj2;
    int left, right, top, bottom, samples, bits, width, height;
    double c, m, y, k;
    double llx, lly, ulx, uly, urx, ury, lrx, lry;
    double tllx, tlly, tulx, tuly, turx, tury, tlrx, tlry;
    double horiz, vert;
    int i, j;
    
    writePS("save\n");
    writePS("/opiMatrix2 matrix currentmatrix def\n");
    writePS("opiMatrix setmatrix\n");
    
    dict->lookup("F", &obj1);
    if (getFileSpec(&obj1, &obj2)) {
        writePSFmt("%ALDImageFileName: {0:t}\n", obj2.getString());
        obj2.free();
    }
    obj1.free();
    
    dict->lookup("CropRect", &obj1);
    if (obj1.isArray() && obj1.arrayGetLength() == 4) {
        obj1.arrayGet(0, &obj2);
        left = obj2.getInt();
        obj2.free();
        obj1.arrayGet(1, &obj2);
        top = obj2.getInt();
        obj2.free();
        obj1.arrayGet(2, &obj2);
        right = obj2.getInt();
        obj2.free();
        obj1.arrayGet(3, &obj2);
        bottom = obj2.getInt();
        obj2.free();
        writePSFmt("%ALDImageCropRect: {0:d} {1:d} {2:d} {3:d}\n",
                   left, top, right, bottom);
    }
    obj1.free();
    
    dict->lookup("Color", &obj1);
    if (obj1.isArray() && obj1.arrayGetLength() == 5) {
        obj1.arrayGet(0, &obj2);
        c = obj2.getNum();
        obj2.free();
        obj1.arrayGet(1, &obj2);
        m = obj2.getNum();
        obj2.free();
        obj1.arrayGet(2, &obj2);
        y = obj2.getNum();
        obj2.free();
        obj1.arrayGet(3, &obj2);
        k = obj2.getNum();
        obj2.free();
        obj1.arrayGet(4, &obj2);
        if (obj2.isString()) {
            writePSFmt("%ALDImageColor: {0:.4g} {1:.4g} {2:.4g} {3:.4g} ",
                       c, m, y, k);
            writePSString(obj2.getString());
            writePS("\n");
        }
        obj2.free();
    }
    obj1.free();
    
    dict->lookup("ColorType", &obj1);
    if (obj1.isName()) {
        writePSFmt("%ALDImageColorType: {0:s}\n", obj1.getName());
    }
    obj1.free();
    
    //~ ignores 'Comments' entry
    //~ need to handle multiple lines
    
    dict->lookup("CropFixed", &obj1);
    if (obj1.isArray()) {
        obj1.arrayGet(0, &obj2);
        ulx = obj2.getNum();
        obj2.free();
        obj1.arrayGet(1, &obj2);
        uly = obj2.getNum();
        obj2.free();
        obj1.arrayGet(2, &obj2);
        lrx = obj2.getNum();
        obj2.free();
        obj1.arrayGet(3, &obj2);
        lry = obj2.getNum();
        obj2.free();
        writePSFmt("%ALDImageCropFixed: {0:.4g} {1:.4g} {2:.4g} {3:.4g}\n",
                   ulx, uly, lrx, lry);
    }
    obj1.free();
    
    dict->lookup("GrayMap", &obj1);
    if (obj1.isArray()) {
        writePS("%ALDImageGrayMap:");
        for (i = 0; i < obj1.arrayGetLength(); i += 16) {
            if (i > 0) {
                writePS("\n%%+");
            }
            for (j = 0; j < 16 && i+j < obj1.arrayGetLength(); ++j) {
                obj1.arrayGet(i+j, &obj2);
                writePSFmt(" {0:d}", obj2.getInt());
                obj2.free();
            }
        }
        writePS("\n");
    }
    obj1.free();
    
    dict->lookup("ID", &obj1);
    if (obj1.isString()) {
        writePSFmt("%ALDImageID: {0:t}\n", obj1.getString());
    }
    obj1.free();
    
    dict->lookup("ImageType", &obj1);
    if (obj1.isArray() && obj1.arrayGetLength() == 2) {
        obj1.arrayGet(0, &obj2);
        samples = obj2.getInt();
        obj2.free();
        obj1.arrayGet(1, &obj2);
        bits = obj2.getInt();
        obj2.free();
        writePSFmt("%ALDImageType: {0:d} {1:d}\n", samples, bits);
    }
    obj1.free();
    
    dict->lookup("Overprint", &obj1);
    if (obj1.isBool()) {
        writePSFmt("%ALDImageOverprint: {0:s}\n",
                   obj1.getBool() ? "true" : "false");
    }
    obj1.free();
    
    dict->lookup("Position", &obj1);
    if (obj1.isArray() && obj1.arrayGetLength() == 8) {
        obj1.arrayGet(0, &obj2);
        llx = obj2.getNum();
        obj2.free();
        obj1.arrayGet(1, &obj2);
        lly = obj2.getNum();
        obj2.free();
        obj1.arrayGet(2, &obj2);
        ulx = obj2.getNum();
        obj2.free();
        obj1.arrayGet(3, &obj2);
        uly = obj2.getNum();
        obj2.free();
        obj1.arrayGet(4, &obj2);
        urx = obj2.getNum();
        obj2.free();
        obj1.arrayGet(5, &obj2);
        ury = obj2.getNum();
        obj2.free();
        obj1.arrayGet(6, &obj2);
        lrx = obj2.getNum();
        obj2.free();
        obj1.arrayGet(7, &obj2);
        lry = obj2.getNum();
        obj2.free();
        opiTransform(state, llx, lly, &tllx, &tlly);
        opiTransform(state, ulx, uly, &tulx, &tuly);
        opiTransform(state, urx, ury, &turx, &tury);
        opiTransform(state, lrx, lry, &tlrx, &tlry);
        writePSFmt("%ALDImagePosition: {0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g} {6:.4g} {7:.4g}\n",
                   tllx, tlly, tulx, tuly, turx, tury, tlrx, tlry);
        obj2.free();
    }
    obj1.free();
    
    dict->lookup("Resolution", &obj1);
    if (obj1.isArray() && obj1.arrayGetLength() == 2) {
        obj1.arrayGet(0, &obj2);
        horiz = obj2.getNum();
        obj2.free();
        obj1.arrayGet(1, &obj2);
        vert = obj2.getNum();
        obj2.free();
        writePSFmt("%ALDImageResoution: {0:.4g} {1:.4g}\n", horiz, vert);
        obj2.free();
    }
    obj1.free();
    
    dict->lookup("Size", &obj1);
    if (obj1.isArray() && obj1.arrayGetLength() == 2) {
        obj1.arrayGet(0, &obj2);
        width = obj2.getInt();
        obj2.free();
        obj1.arrayGet(1, &obj2);
        height = obj2.getInt();
        obj2.free();
        writePSFmt("%ALDImageDimensions: {0:d} {1:d}\n", width, height);
    }
    obj1.free();
    
    //~ ignoring 'Tags' entry
    //~ need to use writePSString() and deal with >255-char lines
    
    dict->lookup("Tint", &obj1);
    if (obj1.isNum()) {
        writePSFmt("%ALDImageTint: {0:.4g}\n", obj1.getNum());
    }
    obj1.free();
    
    dict->lookup("Transparency", &obj1);
    if (obj1.isBool()) {
        writePSFmt("%ALDImageTransparency: {0:s}\n",
                   obj1.getBool() ? "true" : "false");
    }
    obj1.free();
    
    writePS("%%BeginObject: image\n");
    writePS("opiMatrix2 setmatrix\n");
    ++opi13Nest;
}

// Convert PDF user space coordinates to PostScript default user space
// coordinates.  This has to account for both the PDF CTM and the
// PSOutputDev page-fitting transform.
void PSOutputDev::opiTransform(GfxState *state, double x0, double y0,
                               double *x1, double *y1) {
    double t;
    
    state->transform(x0, y0, x1, y1);
    *x1 += tx;
    *y1 += ty;
    if (rotate == 90) {
        t = *x1;
        *x1 = -*y1;
        *y1 = t;
    } else if (rotate == 180) {
        *x1 = -*x1;
        *y1 = -*y1;
    } else if (rotate == 270) {
        t = *x1;
        *x1 = *y1;
        *y1 = -t;
    }
    *x1 *= xScale;
    *y1 *= yScale;
}

void PSOutputDev::opiEnd(GfxState *state, Dict *opiDict) {
    Object dict;
    
    if (globalParams->getPSOPI()) {
        opiDict->lookup("2.0", &dict);
        if (dict.isDict()) {
            writePS("%%EndIncludedImage\n");
            writePS("%%EndOPI\n");
            writePS("grestore\n");
            --opi20Nest;
            dict.free();
        } else {
            dict.free();
            opiDict->lookup("1.3", &dict);
            if (dict.isDict()) {
                writePS("%%EndObject\n");
                writePS("restore\n");
                --opi13Nest;
            }
            dict.free();
        }
    }
}

GBool PSOutputDev::getFileSpec(Object *fileSpec, Object *fileName) {
    if (fileSpec->isString()) {
        fileSpec->copy(fileName);
        return gTrue;
    }
    if (fileSpec->isDict()) {
        fileSpec->dictLookup("DOS", fileName);
        if (fileName->isString()) {
            return gTrue;
        }
        fileName->free();
        fileSpec->dictLookup("Mac", fileName);
        if (fileName->isString()) {
            return gTrue;
        }
        fileName->free();
        fileSpec->dictLookup("Unix", fileName);
        if (fileName->isString()) {
            return gTrue;
        }
        fileName->free();
        fileSpec->dictLookup("F", fileName);
        if (fileName->isString()) {
            return gTrue;
        }
        fileName->free();
    }
    return gFalse;
}
#endif // OPI_SUPPORT

void PSOutputDev::type3D0(GfxState *state, double wx, double wy) {
    writePSFmt("{0:.4g} {1:.4g} setcharwidth\n", wx, wy);
    writePS("q\n");
    t3NeedsRestore = gTrue;
}

void PSOutputDev::type3D1(GfxState *state, double wx, double wy,
                          double llx, double lly, double urx, double ury) {
    t3WX = wx;
    t3WY = wy;
    t3LLX = llx;
    t3LLY = lly;
    t3URX = urx;
    t3URY = ury;
    t3String = new GString();
    writePS("q\n");
    t3Cacheable = gTrue;
    t3NeedsRestore = gTrue;
}

void PSOutputDev::drawForm(Ref id) {
    writePSFmt("f_{0:d}_{1:d}\n", id.num, id.gen);
}

void PSOutputDev::psXObject(Stream *psStream, Stream *level1Stream) {
    Stream *str;
    int c;
    
    if ((level == psLevel1 || level == psLevel1Sep) && level1Stream) {
        str = level1Stream;
    } else {
        str = psStream;
    }
    str->reset();
    while ((c = str->getChar()) != EOF) {
        writePSChar(c);
    }
    str->close();
}

//~ can nextFunc be reset to 0 -- maybe at the start of each page?
//~   or maybe at the start of each color space / pattern?
void PSOutputDev::cvtFunction(Function *func) {
    SampledFunction *func0;
    ExponentialFunction *func2;
    StitchingFunction *func3;
    PostScriptFunction *func4;
    int thisFunc, m, n, nSamples, i, j, k;
    
    switch (func->getType()) {
            
        case -1:			// identity
            writePS("{}\n");
            break;
            
        case 0:			// sampled
            func0 = (SampledFunction *)func;
            thisFunc = nextFunc++;
            m = func0->getInputSize();
            n = func0->getOutputSize();
            nSamples = n;
            for (i = 0; i < m; ++i) {
                nSamples *= func0->getSampleSize(i);
            }
            writePSFmt("/xpdfSamples{0:d} [\n", thisFunc);
            for (i = 0; i < nSamples; ++i) {
                writePSFmt("{0:.4g}\n", func0->getSamples()[i]);
            }
            writePS("] def\n");
            writePSFmt("{{ {0:d} array {1:d} array {2:d} 2 roll\n", 2*m, m, m+2);
            // [e01] [efrac] x0 x1 ... xm-1
            for (i = m-1; i >= 0; --i) {
                // [e01] [efrac] x0 x1 ... xi
                writePSFmt("{0:.4g} sub {1:.4g} mul {2:.4g} add\n",
                           func0->getDomainMin(i),
                           (func0->getEncodeMax(i) - func0->getEncodeMin(i)) /
                           (func0->getDomainMax(i) - func0->getDomainMin(i)),
                           func0->getEncodeMin(i));
                // [e01] [efrac] x0 x1 ... xi-1 xi'
                writePSFmt("dup 0 lt {{ pop 0 }} {{ dup {0:d} gt {{ pop {1:d} }} if }} ifelse\n",
                           func0->getSampleSize(i) - 1, func0->getSampleSize(i) - 1);
                // [e01] [efrac] x0 x1 ... xi-1 xi'
                writePS("dup floor cvi exch dup ceiling cvi exch 2 index sub\n");
                // [e01] [efrac] x0 x1 ... xi-1 floor(xi') ceiling(xi') xi'-floor(xi')
                writePSFmt("{0:d} index {1:d} 3 2 roll put\n", i+3, i);
                // [e01] [efrac] x0 x1 ... xi-1 floor(xi') ceiling(xi')
                writePSFmt("{0:d} index {1:d} 3 2 roll put\n", i+3, 2*i+1);
                // [e01] [efrac] x0 x1 ... xi-1 floor(xi')
                writePSFmt("{0:d} index {1:d} 3 2 roll put\n", i+2, 2*i);
                // [e01] [efrac] x0 x1 ... xi-1
            }
            // [e01] [efrac]
            for (i = 0; i < n; ++i) {
                // [e01] [efrac] y(0) ... y(i-1)
                for (j = 0; j < (1<<m); ++j) {
                    // [e01] [efrac] y(0) ... y(i-1) s(0) s(1) ... s(j-1)
                    writePSFmt("xpdfSamples{0:d}\n", thisFunc);
                    k = m - 1;
                    writePSFmt("{0:d} index {1:d} get\n", i+j+2, 2 * k + ((j >> k) & 1));
                    for (k = m - 2; k >= 0; --k) {
                        writePSFmt("{0:d} mul {1:d} index {2:d} get add\n",
                                   func0->getSampleSize(k),
                                   i + j + 3,
                                   2 * k + ((j >> k) & 1));
                    }
                    if (n > 1) {
                        writePSFmt("{0:d} mul {1:d} add ", n, i);
                    }
                    writePS("get\n");
                }
                // [e01] [efrac] y(0) ... y(i-1) s(0) s(1) ... s(2^m-1)
                for (j = 0; j < m; ++j) {
                    // [e01] [efrac] y(0) ... y(i-1) s(0) s(1) ... s(2^(m-j)-1)
                    for (k = 0; k < (1 << (m - j)); k += 2) {
                        // [e01] [efrac] y(0) ... y(i-1) <k/2 s' values> <2^(m-j)-k s values>
                        writePSFmt("{0:d} index {1:d} get dup\n",
                                   i + k/2 + (1 << (m-j)) - k, j);
                        writePS("3 2 roll mul exch 1 exch sub 3 2 roll mul add\n");
                        writePSFmt("{0:d} 1 roll\n", k/2 + (1 << m-j) - k - 1);
                    }
                    // [e01] [efrac] s'(0) s'(1) ... s(2^(m-j-1)-1)
                }
                // [e01] [efrac] y(0) ... y(i-1) s
                writePSFmt("{0:.4g} mul {1:.4g} add\n",
                           func0->getDecodeMax(i) - func0->getDecodeMin(i),
                           func0->getDecodeMin(i));
                writePSFmt("dup {0:.4g} lt {{ pop {1:.4g} }} {{ dup {2:.4g} gt {{ pop {3:.4g} }} if }} ifelse\n",
                           func0->getRangeMin(i), func0->getRangeMin(i),
                           func0->getRangeMax(i), func0->getRangeMax(i));
                // [e01] [efrac] y(0) ... y(i-1) y(i)
            }
            // [e01] [efrac] y(0) ... y(n-1)
            writePSFmt("{0:d} {1:d} roll pop pop }}\n", n+2, n);
            break;
            
        case 2:			// exponential
            func2 = (ExponentialFunction *)func;
            n = func2->getOutputSize();
            writePSFmt("{{ dup {0:.4g} lt {{ pop {1:.4g} }} {{ dup {2:.4g} gt {{ pop {3:.4g} }} if }} ifelse\n",
                       func2->getDomainMin(0), func2->getDomainMin(0),
                       func2->getDomainMax(0), func2->getDomainMax(0));
            // x
            for (i = 0; i < n; ++i) {
                // x y(0) .. y(i-1)
                writePSFmt("{0:d} index {1:.4g} exp {2:.4g} mul {3:.4g} add\n",
                           i, func2->getE(), func2->getC1()[i] - func2->getC0()[i],
                           func2->getC0()[i]);
                if (func2->getHasRange()) {
                    writePSFmt("dup {0:.4g} lt {{ pop {1:.4g} }} {{ dup {2:.4g} gt {{ pop {3:.4g} }} if }} ifelse\n",
                               func2->getRangeMin(i), func2->getRangeMin(i),
                               func2->getRangeMax(i), func2->getRangeMax(i));
                }
            }
            // x y(0) .. y(n-1)
            writePSFmt("{0:d} {1:d} roll pop }}\n", n+1, n);
            break;
            
        case 3:			// stitching
            func3 = (StitchingFunction *)func;
            thisFunc = nextFunc++;
            for (i = 0; i < func3->getNumFuncs(); ++i) {
                cvtFunction(func3->getFunc(i));
                writePSFmt("/xpdfFunc{0:d}_{1:d} exch def\n", thisFunc, i);
            }
            writePSFmt("{{ dup {0:.4g} lt {{ pop {1:.4g} }} {{ dup {2:.4g} gt {{ pop {3:.4g} }} if }} ifelse\n",
                       func3->getDomainMin(0), func3->getDomainMin(0),
                       func3->getDomainMax(0), func3->getDomainMax(0));
            for (i = 0; i < func3->getNumFuncs() - 1; ++i) {
                writePSFmt("dup {0:.4g} lt {{ {1:.4g} sub {2:.4g} mul {3:.4g} add xpdfFunc{4:d}_{5:d} }} {{\n",
                           func3->getBounds()[i+1],
                           func3->getBounds()[i],
                           func3->getScale()[i],
                           func3->getEncode()[2*i],
                           thisFunc, i);
            }
            writePSFmt("{0:.4g} sub {1:.4g} mul {2:.4g} add xpdfFunc{3:d}_{4:d}\n",
                       func3->getBounds()[i],
                       func3->getScale()[i],
                       func3->getEncode()[2*i],
                       thisFunc, i);
            for (i = 0; i < func3->getNumFuncs() - 1; ++i) {
                writePS("} ifelse\n");
            }
            writePS("}\n");
            break;
            
        case 4:			// PostScript
            func4 = (PostScriptFunction *)func;
            writePS(func4->getCodeString()->getCString());
            writePS("\n");
            break;
    }
}

void PSOutputDev::writePSChar(char c) {
    if (t3String) {
        t3String->append(c);
    } else {
        (*outputFunc)(outputStream, &c, 1);
    }
}

void PSOutputDev::writePS(char *s) {
    if (t3String) {
        t3String->append(s);
    } else {
        (*outputFunc)(outputStream, s, strlen(s));
    }
}

void PSOutputDev::writePSFmt(const char *fmt, ...) {
    va_list args;
    GString *buf;
    
    va_start(args, fmt);
    if (t3String) {
        t3String->appendfv((char *)fmt, args);
    } else {
        buf = GString::formatv((char *)fmt, args);
        (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
        delete buf;
    }
    va_end(args);
}

void PSOutputDev::writePSString(GString *s) {
    Guchar *p;
    int n, line;
    char buf[8];
    
    writePSChar('(');
    line = 1;
    for (p = (Guchar *)s->getCString(), n = s->getLength(); n; ++p, --n) {
        if (line >= 64) {
            writePSChar('\\');
            writePSChar('\n');
            line = 0;
        }
        if (*p == '(' || *p == ')' || *p == '\\') {
            writePSChar('\\');
            writePSChar((char)*p);
            line += 2;
        } else if (*p < 0x20 || *p >= 0x80) {
            sprintf(buf, "\\%03o", *p);
            writePS(buf);
            line += 4;
        } else {
            writePSChar((char)*p);
            ++line;
        }
    }
    writePSChar(')');
}

void PSOutputDev::writePSName(char *s) {
    char *p;
    char c;
    
    p = s;
    while ((c = *p++)) {
        if (c <= (char)0x20 || c >= (char)0x7f ||
            c == '(' || c == ')' || c == '<' || c == '>' ||
            c == '[' || c == ']' || c == '{' || c == '}' ||
            c == '/' || c == '%') {
            writePSFmt("#{0:02x}", c & 0xff);
        } else {
            writePSChar(c);
        }
    }
}

GString *PSOutputDev::filterPSName(GString *name) {
    GString *name2;
    char buf[8];
    int i;
    char c;
    
    name2 = new GString();
    
    // ghostscript chokes on names that begin with out-of-limits
    // numbers, e.g., 1e4foo is handled correctly (as a name), but
    // 1e999foo generates a limitcheck error
    c = name->getChar(0);
    if (c >= '0' && c <= '9') {
        name2->append('f');
    }
    
    for (i = 0; i < name->getLength(); ++i) {
        c = name->getChar(i);
        if (c <= (char)0x20 || c >= (char)0x7f ||
            c == '(' || c == ')' || c == '<' || c == '>' ||
            c == '[' || c == ']' || c == '{' || c == '}' ||
            c == '/' || c == '%') {
            sprintf(buf, "#%02x", c & 0xff);
            name2->append(buf);
        } else {
            name2->append(c);
        }
    }
    return name2;
}

// Write a DSC-compliant <textline>.
void PSOutputDev::writePSTextLine(GString *s) {
    int i, j, step;
    int c;
    
    // - DSC comments must be printable ASCII; control chars and
    //   backslashes have to be escaped (we do cheap Unicode-to-ASCII
    //   conversion by simply ignoring the high byte)
    // - lines are limited to 255 chars (we limit to 200 here to allow
    //   for the keyword, which was emitted by the caller)
    // - lines that start with a left paren are treated as <text>
    //   instead of <textline>, so we escape a leading paren
    if (s->getLength() >= 2 &&
        (s->getChar(0) & 0xff) == 0xfe &&
        (s->getChar(1) & 0xff) == 0xff) {
        i = 3;
        step = 2;
    } else {
        i = 0;
        step = 1;
    }
    for (j = 0; i < s->getLength() && j < 200; i += step) {
        c = s->getChar(i) & 0xff;
        if (c == '\\') {
            writePS("\\\\");
            j += 2;
        } else if (c < 0x20 || c > 0x7e || (j == 0 && c == '(')) {
            writePSFmt("\\{0:03o}", c);
            j += 4;
        } else {
            writePSChar(c);
            ++j;
        }
    }
    writePS("\n");
}
